mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-19 08:01:17 +00:00
Sprite setup scripts for Claude Code and OpenClaw
- OAuth authentication flow with OpenRouter to mint API keys - Automatic sprite creation and environment configuration - Claude Code setup with bypassed onboarding and dark theme - OpenClaw setup with gateway and TUI launch - Shell environment setup (bun PATH, zsh switch)
This commit is contained in:
parent
3eaf6321a1
commit
57bf0ca2fc
4 changed files with 547 additions and 72 deletions
32
README.md
32
README.md
|
|
@ -1,9 +1,17 @@
|
|||
# Spawn
|
||||
|
||||
Automated sprite provisioning scripts for various tools and configurations.
|
||||
Conjure your agents!
|
||||
|
||||
## Usage
|
||||
|
||||
### Claude Code Setup
|
||||
|
||||
Setup a sprite with Claude Code pre-configured (via OpenRouter):
|
||||
|
||||
```bash
|
||||
curl https://openrouter.ai/lab/spawn/sprite/claude.sh | bash
|
||||
```
|
||||
|
||||
### OpenClaw Setup
|
||||
|
||||
Setup a sprite with openclaw pre-configured:
|
||||
|
|
@ -11,25 +19,3 @@ Setup a sprite with openclaw pre-configured:
|
|||
```bash
|
||||
curl https://openrouter.ai/lab/spawn/sprite/openclaw.sh | bash
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Install sprite CLI if not present
|
||||
2. Login to sprite to get credentials
|
||||
3. Provision a new sprite with the specified name
|
||||
4. Add `bun` to PATH
|
||||
5. Install openclaw using bun
|
||||
6. Open openrouter.ai/settings/keys to grab an API key
|
||||
7. Inject API keys (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL)
|
||||
8. Configure openclaw to bypass initial settings
|
||||
|
||||
## Development
|
||||
|
||||
To test locally:
|
||||
|
||||
```bash
|
||||
bash sprite/openclaw.sh
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
These scripts are served from `openrouter.ai/lab/spawn/*` via Next.js rewrites configured in `openrouter-web/projects/web/next.config.mjs`.
|
||||
|
|
|
|||
79
sprite/.claude/rules/index.md
Normal file
79
sprite/.claude/rules/index.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
globs: sprite/**
|
||||
---
|
||||
|
||||
# Sprite CLI Rules
|
||||
|
||||
When building bash scripts that use the Sprite CLI, always follow these guidelines.
|
||||
|
||||
## Documentation Reference
|
||||
|
||||
Always consult the latest [Sprite CLI documentation](https://docs.sprites.dev/cli/commands/) before writing commands.
|
||||
|
||||
## Key Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `sprite login` | Authenticate with Sprites |
|
||||
| `sprite create <name>` | Create a new sprite |
|
||||
| `sprite list` / `sprite ls` | List available sprites |
|
||||
| `sprite exec -s <name> -- <cmd>` | Execute command on sprite (non-interactive) |
|
||||
| `sprite console -s <name>` | Connect to sprite interactively |
|
||||
| `sprite destroy <name>` | Delete a sprite |
|
||||
| `sprite checkpoint create` | Create a checkpoint |
|
||||
| `sprite restore <version>` | Restore from checkpoint |
|
||||
| `sprite proxy <port>` | Forward ports from sprite |
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Use `sprite exec` for non-interactive commands**
|
||||
- Run installation/setup commands with `sprite exec -s <sprite> -- bash -c "<command>"`
|
||||
- Do NOT use `sprite console` for running automated commands
|
||||
|
||||
2. **Use `sprite console` only for interactive sessions**
|
||||
- Reserve `sprite console` for when user interaction is needed
|
||||
- Place `sprite console` at the very end of setup scripts
|
||||
|
||||
3. **Always specify sprite with `-s` flag**
|
||||
- Use `-s <sprite-name>` to target a specific sprite explicitly
|
||||
- Example: `sprite exec -s my-sprite -- ls -la`
|
||||
|
||||
4. **Check sprite existence before creating**
|
||||
- Use `sprite list | grep -q "<name>"` to check if a sprite exists
|
||||
- Only create if it doesn't exist
|
||||
|
||||
5. **Handle login state properly**
|
||||
- Check `sprite list &> /dev/null` to verify authentication
|
||||
- Use `sprite login || true` to prevent script exit on login issues
|
||||
|
||||
6. **Global flags**
|
||||
- `--debug[=<file>]` - Enable debug logging
|
||||
- `-o, --org <name>` - Specify organization
|
||||
- `-s, --sprite <name>` - Specify sprite
|
||||
- `-h, --help` - Show help
|
||||
|
||||
## Script Pattern
|
||||
|
||||
```bash
|
||||
# 1. Check if sprite CLI is installed
|
||||
if ! command -v sprite &> /dev/null; then
|
||||
curl -fsSL https://sprites.dev/install.sh | bash
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
|
||||
# 2. Ensure logged in
|
||||
if ! sprite list &> /dev/null; then
|
||||
sprite login || true
|
||||
fi
|
||||
|
||||
# 3. Create sprite if needed
|
||||
if ! sprite list | grep -q "$SPRITE_NAME"; then
|
||||
sprite create "$SPRITE_NAME"
|
||||
fi
|
||||
|
||||
# 4. Run setup commands via exec
|
||||
sprite exec -s "$SPRITE_NAME" -- bash -c "echo 'Setting up...'"
|
||||
|
||||
# 5. Connect interactively at the very end
|
||||
sprite console "$SPRITE_NAME"
|
||||
```
|
||||
271
sprite/claude.sh
Executable file
271
sprite/claude.sh
Executable file
|
|
@ -0,0 +1,271 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}Claude Code on Sprite${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if sprite is installed, install if not
|
||||
if ! command -v sprite &> /dev/null; then
|
||||
echo -e "${YELLOW}Installing sprite CLI...${NC}"
|
||||
curl -fsSL https://sprites.dev/install.sh | bash
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
|
||||
# Check if already authenticated
|
||||
if ! sprite org list &> /dev/null; then
|
||||
echo -e "${YELLOW}Logging in to sprite...${NC}"
|
||||
sprite login || true
|
||||
fi
|
||||
|
||||
# Ensure user provides a sprite name
|
||||
read -p "Enter sprite name: " SPRITE_NAME < /dev/tty
|
||||
|
||||
# Check if sprite exists, create if not
|
||||
if sprite list 2>/dev/null | grep -q "^${SPRITE_NAME}$\|^${SPRITE_NAME} "; then
|
||||
echo -e "${GREEN}Sprite '$SPRITE_NAME' already exists${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Creating sprite '$SPRITE_NAME'...${NC}"
|
||||
sprite create -skip-console "$SPRITE_NAME" || true
|
||||
echo -e "${YELLOW}Waiting for sprite to be ready...${NC}"
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
# Verify sprite is accessible
|
||||
echo -e "${YELLOW}Verifying sprite connectivity...${NC}"
|
||||
if ! sprite exec -s "$SPRITE_NAME" -- echo "ok" >/dev/null 2>&1; then
|
||||
echo -e "${YELLOW}Sprite not ready, waiting longer...${NC}"
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Setting up sprite environment...${NC}"
|
||||
|
||||
# Helper function to run commands on sprite
|
||||
run_sprite() {
|
||||
sprite exec -s "$SPRITE_NAME" -- bash -c "$1"
|
||||
}
|
||||
|
||||
# 1. Add bun to PATH in .zshrc and .zprofile
|
||||
echo -e "${YELLOW}Configuring shell environment...${NC}"
|
||||
|
||||
# Create temp file with path config
|
||||
PATH_TEMP=$(mktemp)
|
||||
cat > "$PATH_TEMP" << 'EOF'
|
||||
|
||||
# [spawn:path]
|
||||
export PATH="$HOME/.bun/bin:/.sprite/languages/bun/bin:$PATH"
|
||||
EOF
|
||||
|
||||
# Upload and append to shell configs
|
||||
sprite exec -s "$SPRITE_NAME" -file "$PATH_TEMP:/tmp/path_config" -- bash -c "cat /tmp/path_config >> ~/.zprofile && cat /tmp/path_config >> ~/.zshrc && rm /tmp/path_config"
|
||||
rm "$PATH_TEMP"
|
||||
|
||||
# Switch bash to zsh
|
||||
BASH_TEMP=$(mktemp)
|
||||
cat > "$BASH_TEMP" << 'EOF'
|
||||
# [spawn:bash]
|
||||
exec /usr/bin/zsh -l
|
||||
EOF
|
||||
|
||||
sprite exec -s "$SPRITE_NAME" -file "$BASH_TEMP:/tmp/bash_config" -- bash -c "cat /tmp/bash_config > ~/.bash_profile && cat /tmp/bash_config > ~/.bashrc && rm /tmp/bash_config"
|
||||
rm "$BASH_TEMP"
|
||||
|
||||
# 2. Install Claude Code using claude install (reinitializes properly)
|
||||
echo -e "${YELLOW}Installing Claude Code...${NC}"
|
||||
run_sprite "claude install > /dev/null 2>&1"
|
||||
|
||||
# 3. Get OpenRouter API key via OAuth
|
||||
echo ""
|
||||
echo -e "${YELLOW}Authenticating with OpenRouter via OAuth...${NC}"
|
||||
|
||||
CALLBACK_PORT=5180
|
||||
CALLBACK_URL="http://localhost:${CALLBACK_PORT}/callback"
|
||||
AUTH_URL="https://openrouter.ai/auth?callback_url=${CALLBACK_URL}"
|
||||
|
||||
# Create a temporary directory for the OAuth flow
|
||||
OAUTH_DIR=$(mktemp -d)
|
||||
CODE_FILE="$OAUTH_DIR/code"
|
||||
|
||||
# Create an inline script that handles the OAuth callback
|
||||
OAUTH_SCRIPT="$OAUTH_DIR/server.sh"
|
||||
cat > "$OAUTH_SCRIPT" << 'SERVEREOF'
|
||||
#!/bin/bash
|
||||
PORT=$1
|
||||
CODE_FILE=$2
|
||||
|
||||
SUCCESS_HTML='HTTP/1.1 200 OK
|
||||
Content-Type: text/html
|
||||
Connection: close
|
||||
|
||||
<html><body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e;"><div style="text-align: center; color: #fff;"><h1 style="color: #00d4aa;">Authentication Successful!</h1><p>You can close this window and return to your terminal.</p></div></body></html>'
|
||||
|
||||
# Listen for the callback and respond
|
||||
while true; do
|
||||
# Create a temp file for this request
|
||||
REQ_FILE=$(mktemp)
|
||||
|
||||
# Use bash's /dev/tcp to handle the connection (works on macOS and Linux)
|
||||
exec 3<>/dev/tcp/localhost/$PORT 2>/dev/null || {
|
||||
# /dev/tcp not available, fall back to nc with response
|
||||
{ echo "$SUCCESS_HTML"; cat; } | nc -l $PORT > "$REQ_FILE" 2>/dev/null
|
||||
REQUEST=$(head -1 "$REQ_FILE")
|
||||
rm -f "$REQ_FILE"
|
||||
|
||||
if [[ "$REQUEST" == *"/callback?code="* ]]; then
|
||||
CODE=$(echo "$REQUEST" | sed -n 's/.*code=\([^ &]*\).*/\1/p')
|
||||
echo "$CODE" > "$CODE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
continue
|
||||
}
|
||||
done
|
||||
SERVEREOF
|
||||
chmod +x "$OAUTH_SCRIPT"
|
||||
|
||||
echo -e "${YELLOW}Starting local OAuth server on port ${CALLBACK_PORT}...${NC}"
|
||||
|
||||
# Use a simpler nc approach - pipe response while capturing request
|
||||
(
|
||||
SUCCESS_RESPONSE='HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<html><body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e;"><div style="text-align: center; color: #fff;"><h1 style="color: #00d4aa;">Authentication Successful!</h1><p>You can close this window and return to your terminal.</p></div></body></html>'
|
||||
|
||||
while true; do
|
||||
# Listen and capture just the first line of the request, then respond
|
||||
RESPONSE_FILE=$(mktemp)
|
||||
echo -e "$SUCCESS_RESPONSE" > "$RESPONSE_FILE"
|
||||
|
||||
REQUEST=$(nc -l "$CALLBACK_PORT" < "$RESPONSE_FILE" 2>/dev/null | head -1)
|
||||
rm -f "$RESPONSE_FILE"
|
||||
|
||||
if [[ "$REQUEST" == *"/callback?code="* ]]; then
|
||||
CODE=$(echo "$REQUEST" | sed -n 's/.*code=\([^ &]*\).*/\1/p')
|
||||
echo "$CODE" > "$CODE_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
) </dev/null &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Give the server a moment to start
|
||||
sleep 1
|
||||
|
||||
# Open browser
|
||||
echo -e "${YELLOW}Opening browser to authenticate with OpenRouter...${NC}"
|
||||
if command -v open &> /dev/null; then
|
||||
open "$AUTH_URL" </dev/null
|
||||
elif command -v xdg-open &> /dev/null; then
|
||||
xdg-open "$AUTH_URL" </dev/null
|
||||
else
|
||||
echo -e "${YELLOW}Please open: ${AUTH_URL}${NC}"
|
||||
fi
|
||||
|
||||
# Wait for the code file to be created (timeout after 2 minutes)
|
||||
TIMEOUT=120
|
||||
ELAPSED=0
|
||||
while [[ ! -f "$CODE_FILE" ]] && [[ $ELAPSED -lt $TIMEOUT ]]; do
|
||||
sleep 1
|
||||
((ELAPSED++))
|
||||
done
|
||||
|
||||
# Kill the background server process
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
wait $SERVER_PID 2>/dev/null || true
|
||||
|
||||
if [[ ! -f "$CODE_FILE" ]]; then
|
||||
echo -e "${RED}Timed out waiting for OAuth callback${NC}"
|
||||
rm -rf "$OAUTH_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OAUTH_CODE=$(cat "$CODE_FILE")
|
||||
rm -rf "$OAUTH_DIR"
|
||||
|
||||
# Exchange the code for an API key
|
||||
echo -e "${YELLOW}Exchanging OAuth code for API key...${NC}"
|
||||
KEY_RESPONSE=$(curl -s -X POST "https://openrouter.ai/api/v1/auth/keys" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"code\": \"$OAUTH_CODE\"}")
|
||||
|
||||
OPENROUTER_API_KEY=$(echo "$KEY_RESPONSE" | grep -o '"key":"[^"]*"' | sed 's/"key":"//;s/"$//')
|
||||
|
||||
if [[ -z "$OPENROUTER_API_KEY" ]]; then
|
||||
echo -e "${RED}Failed to obtain API key: ${KEY_RESPONSE}${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Successfully obtained OpenRouter API key!${NC}"
|
||||
|
||||
# 4. Inject environment variables
|
||||
echo -e "${YELLOW}Setting up environment variables...${NC}"
|
||||
|
||||
# Create temp file with env config
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
||||
export ANTHROPIC_AUTH_TOKEN="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_API_KEY=""
|
||||
export CLAUDE_CODE_SKIP_ONBOARDING="1"
|
||||
export CLAUDE_CODE_ENABLE_TELEMETRY="0"
|
||||
EOF
|
||||
|
||||
# Upload and append to zshrc
|
||||
sprite exec -s "$SPRITE_NAME" -file "$ENV_TEMP:/tmp/env_config" -- bash -c "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
# 5. Setup Claude Code settings to bypass initial setup
|
||||
echo -e "${YELLOW}Configuring Claude Code...${NC}"
|
||||
|
||||
run_sprite "mkdir -p ~/.claude"
|
||||
|
||||
# Create Claude settings.json via file upload
|
||||
SETTINGS_TEMP=$(mktemp)
|
||||
cat > "$SETTINGS_TEMP" << EOF
|
||||
{
|
||||
"theme": "dark",
|
||||
"editor": "vim",
|
||||
"env": {
|
||||
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
|
||||
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api",
|
||||
"ANTHROPIC_AUTH_TOKEN": "${OPENROUTER_API_KEY}"
|
||||
},
|
||||
"permissions": {
|
||||
"defaultMode": "bypassPermissions",
|
||||
"dangerouslySkipPermissions": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
sprite exec -s "$SPRITE_NAME" -file "$SETTINGS_TEMP:/tmp/claude_settings" -- bash -c "mv /tmp/claude_settings ~/.claude/settings.json"
|
||||
rm "$SETTINGS_TEMP"
|
||||
|
||||
# Create ~/.claude.json global state to skip onboarding and trust dialogs
|
||||
GLOBAL_STATE_TEMP=$(mktemp)
|
||||
cat > "$GLOBAL_STATE_TEMP" << EOF
|
||||
{
|
||||
"hasCompletedOnboarding": true,
|
||||
"bypassPermissionsModeAccepted": true
|
||||
}
|
||||
EOF
|
||||
|
||||
sprite exec -s "$SPRITE_NAME" -file "$GLOBAL_STATE_TEMP:/tmp/claude_global" -- bash -c "mv /tmp/claude_global ~/.claude.json"
|
||||
rm "$GLOBAL_STATE_TEMP"
|
||||
|
||||
# Create empty CLAUDE.md to prevent first-run prompts
|
||||
run_sprite "touch ~/.claude/CLAUDE.md"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Sprite setup completed successfully!${NC}"
|
||||
echo ""
|
||||
|
||||
# Start Claude Code immediately
|
||||
echo -e "${YELLOW}Starting Claude Code...${NC}"
|
||||
sleep 1
|
||||
clear
|
||||
sprite exec -s "$SPRITE_NAME" -tty -- zsh -c "source ~/.zshrc && claude"
|
||||
237
sprite/openclaw.sh
Normal file → Executable file
237
sprite/openclaw.sh
Normal file → Executable file
|
|
@ -7,112 +7,251 @@ GREEN='\033[0;32m'
|
|||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}🚀 Spawnor - OpenClaw Sprite Setup${NC}"
|
||||
echo -e "${GREEN}🚀 Spawn an OpenClaw agent on Sprite${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if sprite is installed
|
||||
# Check if sprite is installed, install if not
|
||||
if ! command -v sprite &> /dev/null; then
|
||||
echo -e "${YELLOW}Installing sprite CLI...${NC}"
|
||||
curl -fsSL https://fly.io/install.sh | sh
|
||||
export PATH="$HOME/.fly/bin:$PATH"
|
||||
curl -fsSL https://sprites.dev/install.sh | bash
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
|
||||
# Login to sprite if not already logged in
|
||||
if ! sprite auth whoami &> /dev/null; then
|
||||
echo -e "${YELLOW}Please login to sprite:${NC}"
|
||||
sprite auth login
|
||||
# Check if already authenticated
|
||||
if ! sprite org list &> /dev/null; then
|
||||
echo -e "${YELLOW}Logging in to sprite...${NC}"
|
||||
sprite login || true
|
||||
fi
|
||||
|
||||
# Get sprite name from user
|
||||
read -p "Enter sprite name: " SPRITE_NAME
|
||||
# Ensure user provides a sprite name
|
||||
read -p "Enter sprite name: " SPRITE_NAME < /dev/tty
|
||||
|
||||
# Check if sprite exists, create if not
|
||||
if sprite list | grep -q "$SPRITE_NAME"; then
|
||||
if sprite list | grep -qx "$SPRITE_NAME"; then
|
||||
echo -e "${GREEN}Sprite '$SPRITE_NAME' already exists${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Creating sprite '$SPRITE_NAME'...${NC}"
|
||||
sprite create "$SPRITE_NAME"
|
||||
sprite create -skip-console "$SPRITE_NAME" || true
|
||||
echo -e "${YELLOW}Waiting for sprite to be ready...${NC}"
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Setting up sprite environment...${NC}"
|
||||
|
||||
# Helper function to run commands on sprite
|
||||
run_sprite() {
|
||||
sprite exec "$SPRITE_NAME" -- bash -c "$1"
|
||||
sprite exec -s "$SPRITE_NAME" -- bash -c "$1"
|
||||
}
|
||||
|
||||
# 1. Add bun to PATH in .zshrc and .zprofile
|
||||
echo -e "${YELLOW}Configuring shell environment...${NC}"
|
||||
|
||||
PATH_CONFIG='
|
||||
# [spawnor:path]
|
||||
# Create temp file with path config
|
||||
PATH_TEMP=$(mktemp)
|
||||
cat > "$PATH_TEMP" << 'EOF'
|
||||
|
||||
# [spawn:path]
|
||||
export PATH="$HOME/.bun/bin:/.sprite/languages/bun/bin:$PATH"
|
||||
'
|
||||
EOF
|
||||
|
||||
# Add to .zprofile for login shells
|
||||
run_sprite "grep -q '\[spawnor:path\]' ~/.zprofile 2>/dev/null || echo '$PATH_CONFIG' >> ~/.zprofile"
|
||||
|
||||
# Add to .zshrc for interactive shells
|
||||
run_sprite "grep -q '\[spawnor:path\]' ~/.zshrc 2>/dev/null || echo '$PATH_CONFIG' >> ~/.zshrc"
|
||||
# Upload and append to shell configs
|
||||
sprite exec -s "$SPRITE_NAME" -file "$PATH_TEMP:/tmp/path_config" -- bash -c "cat /tmp/path_config >> ~/.zprofile && cat /tmp/path_config >> ~/.zshrc && rm /tmp/path_config"
|
||||
rm "$PATH_TEMP"
|
||||
|
||||
# Switch bash to zsh
|
||||
BASH_CONFIG='
|
||||
# [spawnor:bash]
|
||||
BASH_TEMP=$(mktemp)
|
||||
cat > "$BASH_TEMP" << 'EOF'
|
||||
# [spawn:bash]
|
||||
exec /usr/bin/zsh -l
|
||||
'
|
||||
EOF
|
||||
|
||||
run_sprite "grep -q '\[spawnor:bash\]' ~/.bash_profile 2>/dev/null || echo '$BASH_CONFIG' > ~/.bash_profile"
|
||||
run_sprite "grep -q '\[spawnor:bash\]' ~/.bashrc 2>/dev/null || echo '$BASH_CONFIG' > ~/.bashrc"
|
||||
sprite exec -s "$SPRITE_NAME" -file "$BASH_TEMP:/tmp/bash_config" -- bash -c "cat /tmp/bash_config > ~/.bash_profile && cat /tmp/bash_config > ~/.bashrc && rm /tmp/bash_config"
|
||||
rm "$BASH_TEMP"
|
||||
|
||||
# 2. Install openclaw using bun
|
||||
echo -e "${YELLOW}Installing openclaw...${NC}"
|
||||
run_sprite "/.sprite/languages/bun/bin/bun install -g openclaw"
|
||||
|
||||
# 3. Get OpenRouter API key
|
||||
# 3. Get OpenRouter API key via OAuth
|
||||
echo ""
|
||||
echo -e "${YELLOW}Opening openrouter.ai/settings/keys to grab API key...${NC}"
|
||||
echo -e "${YELLOW}Please copy your API key and paste it below${NC}"
|
||||
echo -e "${YELLOW}Authenticating with OpenRouter via OAuth...${NC}"
|
||||
|
||||
# Try to open the browser
|
||||
CALLBACK_PORT=5180
|
||||
CALLBACK_URL="http://localhost:${CALLBACK_PORT}/callback"
|
||||
AUTH_URL="https://openrouter.ai/auth?callback_url=${CALLBACK_URL}"
|
||||
|
||||
# Create a temporary directory for the OAuth flow
|
||||
OAUTH_DIR=$(mktemp -d)
|
||||
CODE_FILE="$OAUTH_DIR/code"
|
||||
|
||||
# Create an inline script that handles the OAuth callback
|
||||
OAUTH_SCRIPT="$OAUTH_DIR/server.sh"
|
||||
cat > "$OAUTH_SCRIPT" << 'SERVEREOF'
|
||||
#!/bin/bash
|
||||
PORT=$1
|
||||
CODE_FILE=$2
|
||||
|
||||
SUCCESS_HTML='HTTP/1.1 200 OK
|
||||
Content-Type: text/html
|
||||
Connection: close
|
||||
|
||||
<html><body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e;"><div style="text-align: center; color: #fff;"><h1 style="color: #00d4aa;">Authentication Successful!</h1><p>You can close this window and return to your terminal.</p></div></body></html>'
|
||||
|
||||
# Listen for the callback and respond
|
||||
while true; do
|
||||
# Create a temp file for this request
|
||||
REQ_FILE=$(mktemp)
|
||||
|
||||
# Use bash's /dev/tcp to handle the connection (works on macOS and Linux)
|
||||
exec 3<>/dev/tcp/localhost/$PORT 2>/dev/null || {
|
||||
# /dev/tcp not available, fall back to nc with response
|
||||
{ echo "$SUCCESS_HTML"; cat; } | nc -l $PORT > "$REQ_FILE" 2>/dev/null
|
||||
REQUEST=$(head -1 "$REQ_FILE")
|
||||
rm -f "$REQ_FILE"
|
||||
|
||||
if [[ "$REQUEST" == *"/callback?code="* ]]; then
|
||||
CODE=$(echo "$REQUEST" | sed -n 's/.*code=\([^ &]*\).*/\1/p')
|
||||
echo "$CODE" > "$CODE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
continue
|
||||
}
|
||||
done
|
||||
SERVEREOF
|
||||
chmod +x "$OAUTH_SCRIPT"
|
||||
|
||||
echo -e "${YELLOW}Starting local OAuth server on port ${CALLBACK_PORT}...${NC}"
|
||||
|
||||
# Use a simpler nc approach - pipe response while capturing request
|
||||
(
|
||||
SUCCESS_RESPONSE='HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<html><body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e;"><div style="text-align: center; color: #fff;"><h1 style="color: #00d4aa;">Authentication Successful!</h1><p>You can close this window and return to your terminal.</p></div></body></html>'
|
||||
|
||||
while true; do
|
||||
# Listen and capture just the first line of the request, then respond
|
||||
RESPONSE_FILE=$(mktemp)
|
||||
echo -e "$SUCCESS_RESPONSE" > "$RESPONSE_FILE"
|
||||
|
||||
REQUEST=$(nc -l "$CALLBACK_PORT" < "$RESPONSE_FILE" 2>/dev/null | head -1)
|
||||
rm -f "$RESPONSE_FILE"
|
||||
|
||||
if [[ "$REQUEST" == *"/callback?code="* ]]; then
|
||||
CODE=$(echo "$REQUEST" | sed -n 's/.*code=\([^ &]*\).*/\1/p')
|
||||
echo "$CODE" > "$CODE_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
) </dev/null &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Give the server a moment to start
|
||||
sleep 1
|
||||
|
||||
# Open browser
|
||||
echo -e "${YELLOW}Opening browser to authenticate with OpenRouter...${NC}"
|
||||
if command -v open &> /dev/null; then
|
||||
open "https://openrouter.ai/settings/keys"
|
||||
open "$AUTH_URL" </dev/null
|
||||
elif command -v xdg-open &> /dev/null; then
|
||||
xdg-open "https://openrouter.ai/settings/keys"
|
||||
xdg-open "$AUTH_URL" </dev/null
|
||||
else
|
||||
echo "Please open: https://openrouter.ai/settings/keys"
|
||||
echo -e "${YELLOW}Please open: ${AUTH_URL}${NC}"
|
||||
fi
|
||||
|
||||
read -sp "Enter your OpenRouter API Key: " OPENROUTER_API_KEY
|
||||
# Wait for the code file to be created (timeout after 2 minutes)
|
||||
TIMEOUT=120
|
||||
ELAPSED=0
|
||||
while [[ ! -f "$CODE_FILE" ]] && [[ $ELAPSED -lt $TIMEOUT ]]; do
|
||||
sleep 1
|
||||
((ELAPSED++))
|
||||
done
|
||||
|
||||
# Kill the background server process
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
wait $SERVER_PID 2>/dev/null || true
|
||||
|
||||
if [[ ! -f "$CODE_FILE" ]]; then
|
||||
echo -e "${RED}Timed out waiting for OAuth callback${NC}"
|
||||
rm -rf "$OAUTH_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OAUTH_CODE=$(cat "$CODE_FILE")
|
||||
rm -rf "$OAUTH_DIR"
|
||||
|
||||
# Exchange the code for an API key
|
||||
echo -e "${YELLOW}Exchanging OAuth code for API key...${NC}"
|
||||
KEY_RESPONSE=$(curl -s -X POST "https://openrouter.ai/api/v1/auth/keys" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"code\": \"$OAUTH_CODE\"}")
|
||||
|
||||
OPENROUTER_API_KEY=$(echo "$KEY_RESPONSE" | grep -o '"key":"[^"]*"' | sed 's/"key":"//;s/"$//')
|
||||
|
||||
if [[ -z "$OPENROUTER_API_KEY" ]]; then
|
||||
echo -e "${RED}Failed to obtain API key: ${KEY_RESPONSE}${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Successfully obtained OpenRouter API key!${NC}"
|
||||
|
||||
# Get model preference
|
||||
echo ""
|
||||
echo -e "${YELLOW}Browse models at: https://openrouter.ai/models${NC}"
|
||||
echo -e "${YELLOW}Which model would you like to use?${NC}"
|
||||
read -p "Enter model ID [openrouter/auto]: " MODEL_ID < /dev/tty
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 4. Inject environment variables
|
||||
echo -e "${YELLOW}Setting up environment variables...${NC}"
|
||||
|
||||
ENV_CONFIG="
|
||||
# [spawnor:env]
|
||||
export OPENROUTER_API_KEY=\"$OPENROUTER_API_KEY\"
|
||||
export ANTHROPIC_API_KEY=\"$OPENROUTER_API_KEY\"
|
||||
export ANTHROPIC_BASE_URL=\"https://openrouter.ai/api\"
|
||||
"
|
||||
# Create temp file with env config
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
run_sprite "grep -q '\[spawnor:env\]' ~/.zshrc 2>/dev/null || echo '$ENV_CONFIG' >> ~/.zshrc"
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
||||
EOF
|
||||
|
||||
# Upload and append to zshrc
|
||||
sprite exec -s "$SPRITE_NAME" -file "$ENV_TEMP:/tmp/env_config" -- bash -c "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
# 5. Setup openclaw to bypass initial settings
|
||||
echo -e "${YELLOW}Configuring openclaw...${NC}"
|
||||
|
||||
run_sprite "mkdir -p ~/.config/openclaw"
|
||||
# Remove old config and create fresh
|
||||
run_sprite "rm -rf ~/.openclaw && mkdir -p ~/.openclaw"
|
||||
|
||||
# Generate a random gateway token
|
||||
GATEWAY_TOKEN=$(openssl rand -hex 16)
|
||||
|
||||
OPENCLAW_CONFIG='{
|
||||
"hasCompletedOnboarding": true,
|
||||
"defaultProvider": "openrouter",
|
||||
"apiKey": "'"$OPENROUTER_API_KEY"'",
|
||||
"baseUrl": "https://openrouter.ai/api"
|
||||
"env": {
|
||||
"OPENROUTER_API_KEY": "'"$OPENROUTER_API_KEY"'"
|
||||
},
|
||||
"gateway": {
|
||||
"mode": "local",
|
||||
"auth": {
|
||||
"token": "'"$GATEWAY_TOKEN"'"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": {
|
||||
"primary": "openrouter/'"$MODEL_ID"'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'
|
||||
|
||||
run_sprite "echo '$OPENCLAW_CONFIG' > ~/.config/openclaw/config.json"
|
||||
run_sprite "echo '$OPENCLAW_CONFIG' > ~/.openclaw/openclaw.json"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Sprite setup completed successfully!${NC}"
|
||||
echo ""
|
||||
echo -e "Connect to your sprite with: ${YELLOW}sprite console $SPRITE_NAME${NC}"
|
||||
echo ""
|
||||
|
||||
# Start openclaw gateway in background and run openclaw tui
|
||||
echo -e "${YELLOW}Starting openclaw...${NC}"
|
||||
sprite exec -s "$SPRITE_NAME" -- zsh -c "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
sleep 2
|
||||
sprite exec -s "$SPRITE_NAME" -tty -- zsh -c "source ~/.zshrc && openclaw tui"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue