Changed trap commands from double quotes to single quotes so variables expand at trap execution time instead of definition time. This prevents security issues where variables could be tampered with between trap definition and execution. Fixed 3 instances: - cli/install.sh (2 instances): trap 'rm -rf "$tmpdir"' EXIT - test/run.sh (1 instance): trap 'cleanup' EXIT Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| .gitignore | ||
| install.sh | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| spawn.sh | ||
| tsconfig.json | ||
| vitest.config.ts | ||
Spawn CLI
The spawn CLI is a command-line tool for launching AI coding agents on cloud providers, pre-configured with OpenRouter.
Overview
The spawn CLI provides a unified interface to:
- Launch any supported AI agent (Claude Code, Aider, etc.) on any supported cloud provider
- Interactively browse available agents and clouds
- View the agent × cloud compatibility matrix
- Self-update to the latest version
Architecture
Three-Tier Installation Strategy
The CLI uses a progressive fallback installation strategy to maximize compatibility:
┌─────────────────────────────────────────────────────────┐
│ Method 1: Bun (Preferred) │
│ - Fastest execution (native TypeScript runtime) │
│ - Full TypeScript support with minimal overhead │
│ - Falls back to compiled binary if global install fails │
└─────────────────────────────────────────────────────────┘
↓ (if bun not found)
┌─────────────────────────────────────────────────────────┐
│ Method 2: npm │
│ - Standard Node.js package manager │
│ - Transpiles TypeScript to JavaScript at install time │
│ - Requires Node.js runtime │
└─────────────────────────────────────────────────────────┘
↓ (if npm not found)
┌─────────────────────────────────────────────────────────┐
│ Method 3: Bash Fallback │
│ - Pure bash implementation (spawn.sh) │
│ - Zero runtime dependencies except curl + jq/python3 │
│ - Functional subset of TypeScript CLI │
└─────────────────────────────────────────────────────────┘
Why this pattern?
- Universal compatibility: Works on any system with bash and curl
- Optimal performance: Uses the fastest available runtime (bun > node > bash)
- Zero friction: No prerequisite installation required for basic usage
- Graceful degradation: Each tier provides full functionality with varying performance characteristics
Directory Structure
cli/
├── src/
│ ├── index.ts # Entry point (routes commands to handlers)
│ ├── commands.ts # All command implementations
│ ├── manifest.ts # Manifest fetching and caching logic
│ └── version.ts # Version constant
├── install.sh # Multi-tier installer script
├── spawn.sh # Bash fallback CLI (full implementation)
├── package.json # npm package metadata
└── tsconfig.json # TypeScript configuration
TypeScript Implementation
The TypeScript CLI (src/*.ts) provides:
- Interactive mode: Terminal UI with prompts for selecting agents and clouds
- Manifest caching: Local cache with TTL to minimize network requests
- Progress indicators: Spinners and colored output for better UX
- Error handling: Structured error messages and exit codes
Key dependencies:
@clack/prompts— Interactive terminal promptspicocolors— Terminal color support
Bash Fallback Implementation
The bash CLI (spawn.sh) is a standalone script that:
- Implements the same commands as the TypeScript version
- Uses
jqorpython3for JSON parsing (auto-detects which is available) - Provides a numbered menu picker for interactive mode
- Maintains local manifest cache with TTL
- Supports all core commands:
list,agents,clouds,run,improve,update
Why maintain both implementations?
- Portability: Bash version works on minimal systems (CI containers, embedded Linux, etc.)
- Bootstrap: Used by installer when bun/npm aren't available
- Reference: Demonstrates that the protocol is runtime-agnostic
Installation
Quick Install
curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cli/install.sh | bash
The installer will:
- Check for
bun→ install viabun install -gif found - Check for
npm→ install vianpm install -gif found - Fallback → download
spawn.shto$HOME/.local/binif neither found
Environment Variables
SPAWN_INSTALL_DIR— Override install directory (default:$HOME/.local/binfor fallback method)
Manual Installation (Development)
cd cli
bun install
bun link
Or build a standalone binary:
bun run compile # Creates ./spawn executable
Usage
Interactive Mode
spawn
Launches an interactive picker to select an agent and cloud provider.
Direct Launch
spawn <agent> <cloud>
Examples:
spawn claude sprite # Launch Claude Code on Sprite
spawn aider hetzner # Launch Aider on Hetzner Cloud
Agent Information
spawn <agent>
Show which cloud providers support the specified agent.
Example:
spawn claude
# Output:
# Claude Code — AI coding agent from Anthropic
#
# Available clouds:
# Sprite spawn claude sprite
# Hetzner Cloud spawn claude hetzner
List All Combinations
spawn list
Display the full agent × cloud compatibility matrix.
List Agents
spawn agents
Show all available agents with descriptions.
List Cloud Providers
spawn clouds
Show all available cloud providers with descriptions.
Improve Command
spawn improve [--loop]
Clone (or update) the spawn repository and run the improve.sh script, which uses Claude to autonomously add missing matrix entries or new agents/clouds.
Update CLI
spawn update
- TypeScript version: Displays update instructions (re-run installer)
- Bash version: Self-updates by downloading the latest
spawn.sh
Version
spawn version
Display the current CLI version.
Development
Prerequisites
- Bun 1.0+ (or Node.js 18+ with npm)
- TypeScript 5.0+
Running Locally
bun run dev # Run TypeScript CLI directly
bun run build # Build to cli.js
bun run compile # Compile to standalone binary
Testing
# Test TypeScript version
bun run dev list
bun run dev agents
bun run dev claude sprite
# Test bash version
bash spawn.sh list
bash spawn.sh agents
bash spawn.sh claude sprite
Code Organization
src/index.ts
- Command-line argument parsing
- Routes to appropriate command handler
- Minimal logic (just dispatching)
src/commands.ts
- All command implementations
- Interactive picker UI
- Script execution logic
- Help text
src/manifest.ts
- Manifest fetching from GitHub
- Local caching with TTL
- Offline fallback to stale cache
- Typed manifest structure
src/version.ts
- Single source of truth for version number
- Imported by both TypeScript and bash implementations
Adding a New Command
-
Add command handler in
src/commands.ts:export async function cmdMyCommand() { const manifest = await loadManifest(); // ... implementation } -
Add routing in
src/index.ts:case "mycommand": await cmdMyCommand(); break; -
Update help text in
src/commands.ts→cmdHelp() -
(Optional) Add equivalent implementation to
spawn.shfor bash fallback
Design Rationale
Why TypeScript?
- Type safety: Manifest structure is type-checked at compile time
- Modern async/await: Clean, readable asynchronous code
- Rich ecosystem: Access to high-quality CLI libraries (
@clack/prompts, etc.) - Single codebase: Same code runs on bun, node, or as a compiled binary
Why Bash Fallback?
- Universality: Bash is available on virtually all Unix-like systems
- Zero dependencies: Only requires
curlandjq/python3(one of which is usually installed) - CI/CD friendly: Works in minimal Docker containers, GitHub Actions, etc.
- Educational: Demonstrates the protocol can be implemented in any language
Why Bun → npm → Bash Tiering?
- Performance gradient: Bun is fastest, npm is widely available, bash always works
- User experience: Bun users get instant execution, others get working tool
- Distribution: Can be installed via package manager or curl | bash
- Maintenance: Single TypeScript codebase serves bun and npm, bash is separate but synchronized
Manifest Caching
Both implementations cache the manifest locally to reduce network requests:
- Cache location:
$XDG_CACHE_HOME/spawn/manifest.json(or~/.cache/spawn/manifest.json) - TTL: 1 hour (3600 seconds)
- Offline fallback: If fetch fails, uses stale cache if available
- Invalidation:
spawn updateclears the cache
Script Execution Flow
When you run spawn <agent> <cloud>:
- Load manifest: Fetch from GitHub or use cached version
- Validate combination: Check that
matrix["<cloud>/<agent>"]is"implemented" - Download script: Fetch
https://openrouter.ai/lab/spawn/<cloud>/<agent>.sh- Fallback to GitHub raw URL if OpenRouter CDN fails
- Execute: Pipe script to
bash -cwith inherited stdio - Interactive handoff: User interacts directly with the spawned agent
Contributing
Before Submitting Changes
-
Test both TypeScript and bash versions:
bun run dev --help bash spawn.sh --help -
Ensure version numbers are synchronized:
src/version.ts→VERSIONspawn.sh→SPAWN_VERSIONpackage.json→version
-
Update this README if you add new commands or change behavior
-
Run the installer locally to verify the three-tier strategy works:
# Test with bun bash install.sh # Test without bun (rename temporarily) mv $(which bun) $(which bun).bak bash install.sh mv $(which bun).bak $(which bun)
Release Checklist
- Bump version in all three locations (see above)
- Update CHANGELOG (if exists)
- Test installer on clean system
- Tag release:
git tag -a cli-vX.Y.Z -m "Release vX.Y.Z" - Push tag:
git push --tags
License
See repository root for license information.