fix: Improve CLI error handling, fix bash compat, and update cloud READMEs (#90)

- Show clear error when --prompt/-p or --prompt-file is used without a
  value (previously silently ignored)
- Fix --prompt-file splice index bug when used after --prompt
- Replace echo -e with printf in fly/lib/common.sh for macOS bash 3.x
  compatibility
- Fix incorrect env var name in README (DIGITALOCEAN_TOKEN -> DO_API_TOKEN)
- Add missing agent entries (gptme, OpenCode, Plandex) to 11 cloud READMEs
- Add all 13 agents to Civo README (previously only had 3)

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-09 09:33:57 -08:00 committed by GitHub
parent 66701d3cf9
commit 2915d7bca6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 260 additions and 10 deletions

View file

@ -68,7 +68,7 @@ export OPENROUTER_API_KEY=sk-or-v1-xxxxx
# Cloud-specific credentials (varies by provider)
export SPRITE_API_KEY=... # For Sprite
export HCLOUD_TOKEN=... # For Hetzner
export DIGITALOCEAN_TOKEN=... # For DigitalOcean
export DO_API_TOKEN=... # For DigitalOcean
# Run non-interactively
spawn claude sprite

View file

@ -66,6 +66,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/aws-lightsail/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/aws-lightsail/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/aws-lightsail/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/aws-lightsail/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/aws-lightsail/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -10,18 +10,78 @@ Civo cloud-native instances via REST API. [Civo](https://www.civo.com/)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/claude.sh)
```
#### OpenClaw
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/openclaw.sh)
```
#### NanoClaw
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/nanoclaw.sh)
```
#### Aider
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/aider.sh)
```
#### Goose
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/goose.sh)
```
#### Codex CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/codex.sh)
```
#### Open Interpreter
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/interpreter.sh)
```
#### Gemini CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/gemini.sh)
```
#### Amazon Q CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/amazonq.sh)
```
#### Cline
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/civo/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -56,23 +56,33 @@ async function main(): Promise<void> {
let prompt: string | undefined;
let filteredArgs = [...args];
const promptIndex = args.findIndex(arg => arg === "--prompt" || arg === "-p");
if (promptIndex !== -1 && args[promptIndex + 1]) {
prompt = args[promptIndex + 1];
const promptIndex = filteredArgs.findIndex(arg => arg === "--prompt" || arg === "-p");
if (promptIndex !== -1) {
if (!filteredArgs[promptIndex + 1] || filteredArgs[promptIndex + 1].startsWith("-")) {
console.error(`Error: ${filteredArgs[promptIndex]} requires a value`);
console.error(`\nUsage: spawn <agent> <cloud> ${filteredArgs[promptIndex]} "your prompt here"`);
process.exit(1);
}
prompt = filteredArgs[promptIndex + 1];
// Remove --prompt and its value from args
filteredArgs.splice(promptIndex, 2);
}
// Extract --prompt-file flag
const promptFileIndex = args.findIndex(arg => arg === "--prompt-file");
if (promptFileIndex !== -1 && args[promptFileIndex + 1]) {
const promptFileIndex = filteredArgs.findIndex(arg => arg === "--prompt-file");
if (promptFileIndex !== -1) {
if (!filteredArgs[promptFileIndex + 1] || filteredArgs[promptFileIndex + 1].startsWith("-")) {
console.error(`Error: --prompt-file requires a file path`);
console.error(`\nUsage: spawn <agent> <cloud> --prompt-file instructions.txt`);
process.exit(1);
}
const { readFileSync } = await import("fs");
try {
prompt = readFileSync(args[promptFileIndex + 1], "utf-8");
prompt = readFileSync(filteredArgs[promptFileIndex + 1], "utf-8");
// Remove --prompt-file and its value from args
filteredArgs.splice(promptFileIndex, 2);
} catch (err) {
console.error(`Error reading prompt file '${args[promptFileIndex + 1]}': ${err && typeof err === "object" && "message" in err ? err.message : String(err)}`);
console.error(`Error reading prompt file '${filteredArgs[promptFileIndex + 1]}': ${err && typeof err === "object" && "message" in err ? err.message : String(err)}`);
console.error(`\nMake sure the file exists and is readable.`);
process.exit(1);
}

View file

@ -64,6 +64,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/digitalocean/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/digitalocean/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/digitalocean/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/digitalocean/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/digitalocean/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -66,6 +66,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/e2b/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/e2b/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/e2b/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/e2b/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/e2b/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -123,8 +123,8 @@ ensure_fly_token() {
# 4. Prompt and validate
echo ""
log_warn "Fly.io API Token Required"
echo -e "${YELLOW}Get your token by running: fly tokens deploy${NC}"
echo -e "${YELLOW}Or create one at: https://fly.io/dashboard → Tokens${NC}"
printf '%b\n' "${YELLOW}Get your token by running: fly tokens deploy${NC}"
printf '%b\n' "${YELLOW}Or create one at: https://fly.io/dashboard → Tokens${NC}"
echo ""
local token

View file

@ -66,6 +66,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/gcp/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/gcp/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/gcp/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/gcp/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/gcp/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -64,6 +64,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -66,6 +66,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/lambda/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/lambda/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/lambda/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/lambda/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/lambda/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -64,6 +64,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/linode/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/linode/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/linode/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/linode/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/linode/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -66,6 +66,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/modal/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/modal/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/modal/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/modal/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/modal/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -64,6 +64,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/plandex.sh)
```
## Non-Interactive Mode
```bash

View file

@ -64,6 +64,24 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/vultr/amazonq.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/vultr/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/vultr/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/vultr/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/vultr/plandex.sh)
```
## Non-Interactive Mode
```bash