mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-22 03:14:57 +00:00
Add guardrails: CLAUDE.md rules, hooks, pre-commit validation (#33)
* feat: add gptme agent to spawn matrix Add gptme (https://github.com/gptme/gptme) - a personal AI agent in the terminal with tools for code editing, terminal commands, web browsing, and more. Natively supports OpenRouter via OPENROUTER_API_KEY. - Add gptme agent entry to manifest.json with OpenRouter env vars - Implement sprite/gptme.sh deployment script - Implement hetzner/gptme.sh deployment script - Add "missing" matrix entries for remaining 8 clouds - Update README.md with usage instructions for Sprite and Hetzner Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add Fly.io cloud provider with claude and aider agents Add Fly.io as a new cloud provider using the Machines REST API for provisioning and flyctl CLI for SSH access. Docker-based machines with pay-per-second pricing. - Create fly/lib/common.sh with Fly.io Machines API integration - Implement fly/claude.sh for Claude Code deployment - Implement fly/aider.sh for Aider deployment - Update README.md with Fly.io usage instructions and env vars Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add gemini, amazonq, cline, gptme to Fly.io Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add openclaw, nanoclaw, goose, codex, interpreter to Fly.io Implements 5 new agent scripts for the Fly.io cloud provider: - fly/openclaw.sh: OpenClaw with gateway + TUI, model selection, config - fly/nanoclaw.sh: NanoClaw WhatsApp agent with .env configuration - fly/goose.sh: Block's Goose agent with OpenRouter provider - fly/codex.sh: OpenAI Codex CLI with OpenRouter base URL override - fly/interpreter.sh: Open Interpreter with OpenRouter base URL override All scripts follow the Fly.io pattern (flyctl-based, no IP args for run_server/interactive_session) and use upload_file for env injection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add gptme agent to 8 remaining clouds Implement gptme agent scripts for digitalocean, vultr, linode, lambda, aws-lightsail, gcp, e2b, and modal. Each script follows the exact pattern of that cloud's existing aider.sh, adapted for gptme's install and launch commands. Updates manifest.json matrix entries from "missing" to "implemented". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add guardrails from insights: CLAUDE.md rules, hooks, pre-commit Based on usage insights analysis: CLAUDE.md: - Shell script rules: curl|bash compat, macOS bash 3.x compat - Autonomous loop rules: test after each iteration, never revert fixes - Git workflow rules: always use feature branches .claude/settings.json: - PostToolUse hook validates .sh files on every Write/Edit: syntax check, no relative source, no echo -e, no set -u .githooks/pre-commit: - Blocks commits with: syntax errors, relative sources, echo -e, set -euo, references to deleted functions - Install: git config core.hooksPath .githooks README.md: - Added developer setup section with hook installation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Sprite <noreply@sprite.dev> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ce0f2ce7fb
commit
b6ee6b6ab1
27 changed files with 2069 additions and 10 deletions
|
|
@ -5,5 +5,13 @@
|
|||
"permissions": {
|
||||
"defaultMode": "bypassPermissions",
|
||||
"dangerouslySkipPermissions": true
|
||||
},
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"command": "bash -c 'FILE=\"$CLAUDE_FILE\"; if [[ \"$FILE\" == *.sh ]]; then bash -n \"$FILE\" 2>&1 || { echo \"SYNTAX ERROR in $FILE\"; exit 2; }; if grep -qn \"source \\.\\./\\|source \\./\" \"$FILE\" 2>/dev/null; then echo \"RELATIVE SOURCE detected in $FILE — breaks curl|bash execution\"; exit 2; fi; if grep -qn \"echo -e \" \"$FILE\" 2>/dev/null; then echo \"echo -e detected in $FILE — use printf instead (macOS bash 3.x compat)\"; exit 2; fi; if grep -qn \"set -.*u\" \"$FILE\" 2>/dev/null && ! grep -qn \"set -eo pipefail\" \"$FILE\" 2>/dev/null; then echo \"set -u (nounset) detected in $FILE — use set -eo pipefail instead\"; exit 2; fi; fi'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
.githooks/pre-commit
Executable file
64
.githooks/pre-commit
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/bash
|
||||
# Pre-commit hook: validates all staged .sh files
|
||||
# Install: git config core.hooksPath .githooks
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
|
||||
errors=0
|
||||
|
||||
# Get staged .sh files
|
||||
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$' || true)
|
||||
|
||||
if [[ -z "$staged_files" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Validating staged shell scripts..."
|
||||
|
||||
for file in $staged_files; do
|
||||
# 1. Syntax check
|
||||
if ! bash -n "$file" 2>/dev/null; then
|
||||
echo -e "${RED}FAIL${NC} $file: syntax error"
|
||||
bash -n "$file" 2>&1 | head -3
|
||||
errors=$((errors + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# 2. No relative source (breaks curl|bash)
|
||||
if grep -qn 'source \.\./' "$file" 2>/dev/null || grep -qn 'source \./' "$file" 2>/dev/null; then
|
||||
echo -e "${RED}FAIL${NC} $file: relative source path (breaks curl|bash)"
|
||||
grep -n 'source \.\.' "$file" 2>/dev/null || true
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# 3. No echo -e (breaks macOS bash 3.x)
|
||||
if grep -qn 'echo -e ' "$file" 2>/dev/null; then
|
||||
echo -e "${RED}FAIL${NC} $file: echo -e (use printf for macOS compat)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# 4. No set -u / set -euo (breaks env var checks)
|
||||
if grep -qn 'set -euo' "$file" 2>/dev/null; then
|
||||
echo -e "${RED}FAIL${NC} $file: set -euo pipefail (drop the 'u', use set -eo pipefail)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# 5. Check for calls to deleted functions
|
||||
if grep -qn 'write_oauth_response_file\|create_oauth_response_html' "$file" 2>/dev/null; then
|
||||
echo -e "${RED}FAIL${NC} $file: references deleted function"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $errors -gt 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${RED}$errors error(s) found. Commit blocked.${NC}"
|
||||
echo "Fix the issues above and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}All $( echo "$staged_files" | wc -w | tr -d ' ') scripts passed validation.${NC}"
|
||||
60
CLAUDE.md
60
CLAUDE.md
|
|
@ -164,18 +164,60 @@ This pattern ensures:
|
|||
- Path resolution works when sourced from any location
|
||||
- Script fails fast if shared library is missing
|
||||
|
||||
## Script Conventions
|
||||
## Shell Script Rules
|
||||
|
||||
- `#!/bin/bash` + `set -e`
|
||||
- Source `lib/common.sh` with local-first, remote-fallback pattern
|
||||
- Use `OPENROUTER_API_KEY` env var to skip OAuth when set
|
||||
- All env vars documented in README.md under the relevant section
|
||||
These rules are **non-negotiable** — violating them breaks remote execution for all users.
|
||||
|
||||
### curl|bash Compatibility
|
||||
Every script MUST work when executed via `bash <(curl -fsSL URL)`:
|
||||
- **NEVER** use relative paths for sourcing (`source ./lib/...`, `source ../shared/...`)
|
||||
- **NEVER** rely on `$0`, `dirname $0`, or `BASH_SOURCE` resolving to a real filesystem path
|
||||
- **ALWAYS** use the local-or-remote fallback pattern:
|
||||
```bash
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/{cloud}/lib/common.sh)"
|
||||
fi
|
||||
```
|
||||
- Similarly, `{cloud}/lib/common.sh` MUST use the same fallback for `shared/common.sh`
|
||||
|
||||
### macOS bash 3.x Compatibility
|
||||
macOS ships bash 3.2. All scripts MUST work on it:
|
||||
- **NO** `echo -e` — use `printf` for escape sequences
|
||||
- **NO** `source <(cmd)` inside `bash <(curl ...)` — use `eval "$(cmd)"` instead
|
||||
- **NO** `((var++))` with `set -e` — use `var=$((var + 1))` (avoids falsy-zero exit)
|
||||
- **NO** `local` keyword inside `( ... ) &` subshells — not function scope
|
||||
- **NO** `set -u` (nounset) — use `${VAR:-}` for optional env var checks instead
|
||||
|
||||
### Conventions
|
||||
- `#!/bin/bash` + `set -eo pipefail` (no `u` flag)
|
||||
- Use `${VAR:-}` for all optional env var checks (`OPENROUTER_API_KEY`, cloud tokens, etc.)
|
||||
- Remote fallback URL: `https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/{path}`
|
||||
- Scripts must be runnable via: `bash <(curl -fsSL https://openrouter.ai/lab/spawn/{cloud}/{agent}.sh)`
|
||||
- All env vars documented in the cloud's README.md
|
||||
|
||||
## Autonomous Loops
|
||||
|
||||
When running autonomous improvement/refactoring loops (`./improve.sh --loop`):
|
||||
|
||||
- **Run `bash -n` on every changed .sh file** before committing — syntax errors break everything
|
||||
- **NEVER revert a prior fix** — if `shared/common.sh` was changed to fix macOS compat, don't undo it
|
||||
- **NEVER re-introduce deleted functions** — if `write_oauth_response_file` was removed, don't call it
|
||||
- **NEVER change the source/eval fallback pattern** in lib/common.sh files — it's load-bearing for curl|bash
|
||||
- **Test after EACH iteration** — don't batch multiple changes without verification
|
||||
- **If a change breaks tests, STOP** — revert and ask for guidance rather than compounding the regression
|
||||
|
||||
## Git Workflow
|
||||
|
||||
- Always work on a feature branch — never commit directly to main (except urgent one-line fixes)
|
||||
- Before creating a PR, check `git status` and `git log` to verify branch state
|
||||
- Use `gh pr create` from the feature branch, then `gh pr merge --squash`
|
||||
- Never rebase main or use `--force` unless explicitly asked
|
||||
|
||||
## After Each Change
|
||||
|
||||
1. Update `manifest.json` matrix status to `"implemented"`
|
||||
2. Update `README.md` with usage instructions
|
||||
3. Run `bash test/run.sh` if tests exist for the cloud
|
||||
1. `bash -n {file}` syntax check on all modified scripts
|
||||
2. Update `manifest.json` matrix status to `"implemented"`
|
||||
3. Update the cloud's `README.md` with usage instructions
|
||||
4. Commit with a descriptive message
|
||||
|
|
|
|||
60
README.md
60
README.md
|
|
@ -53,6 +53,12 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/codex.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/interpreter.sh)
|
||||
```
|
||||
|
||||
#### gptme
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/sprite/gptme.sh)
|
||||
```
|
||||
|
||||
### Non-Interactive Mode
|
||||
|
||||
For automation or CI/CD, set environment variables:
|
||||
|
|
@ -135,6 +141,12 @@ bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/codex.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/interpreter.sh)
|
||||
```
|
||||
|
||||
#### gptme
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/hetzner/gptme.sh)
|
||||
```
|
||||
|
||||
### Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
|
|
@ -351,6 +363,43 @@ OPENROUTER_API_KEY=sk-or-v1-xxxxx \
|
|||
|
||||
---
|
||||
|
||||
## Fly.io
|
||||
|
||||
Spawn agents on [Fly.io](https://fly.io) Machines via REST API and flyctl CLI. Docker-based VMs with pay-per-second pricing.
|
||||
|
||||
### Usage
|
||||
|
||||
#### Claude Code
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/fly/claude.sh)
|
||||
```
|
||||
|
||||
#### Aider
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/fly/aider.sh)
|
||||
```
|
||||
|
||||
### Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
FLY_APP_NAME=dev-mk1 \
|
||||
FLY_API_TOKEN=your-fly-api-token \
|
||||
OPENROUTER_API_KEY=sk-or-v1-xxxxx \
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/fly/claude.sh)
|
||||
```
|
||||
|
||||
**Environment Variables:**
|
||||
- `FLY_APP_NAME` - Name for the Fly app (skips prompt)
|
||||
- `FLY_API_TOKEN` - Fly.io API token (skips prompt, saved to `~/.config/spawn/fly.json`)
|
||||
- `OPENROUTER_API_KEY` - Skip OAuth and use this API key directly
|
||||
- `FLY_REGION` - Deployment region (default: `iad`)
|
||||
- `FLY_VM_MEMORY` - VM memory in MB (default: `1024`)
|
||||
- `FLY_ORG` - Fly.io organization slug (default: `personal`)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
Spawn uses a **shared library pattern** to reduce code duplication across cloud providers:
|
||||
|
|
@ -398,6 +447,16 @@ spawn/
|
|||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/OpenRouterTeam/spawn.git
|
||||
cd spawn
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
The pre-commit hook validates all staged `.sh` files: syntax check, no relative sources, no `echo -e`, no `set -u`, no references to deleted functions.
|
||||
|
||||
### Running ShellCheck Locally
|
||||
|
||||
Spawn uses [ShellCheck](https://www.shellcheck.net/) to lint all bash scripts and catch common mistakes.
|
||||
|
|
@ -444,6 +503,7 @@ Spawn stores cloud provider API tokens and OpenRouter API keys locally in JSON f
|
|||
- `digitalocean.json` - DigitalOcean API token
|
||||
- `vultr.json` - Vultr API key
|
||||
- `linode.json` - Linode API token
|
||||
- `fly.json` - Fly.io API token
|
||||
- OpenRouter API keys stored in shell config files (`~/.bashrc`, `~/.zshrc`)
|
||||
|
||||
**Security Posture:**
|
||||
|
|
|
|||
72
aws-lightsail/gptme.sh
Normal file
72
aws-lightsail/gptme.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/aws-lightsail/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on AWS Lightsail"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure AWS CLI is configured
|
||||
ensure_aws_cli
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get instance name and create server
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "$LIGHTSAIL_SERVER_IP"
|
||||
wait_for_cloud_init "$LIGHTSAIL_SERVER_IP"
|
||||
|
||||
# 5. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$LIGHTSAIL_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
# 6. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 7. Get model preference
|
||||
echo ""
|
||||
log_warn "Browse models at: https://openrouter.ai/models"
|
||||
log_warn "Which model would you like to use with gptme?"
|
||||
MODEL_ID=$(safe_read "Enter model ID [openrouter/auto]: ") || MODEL_ID=""
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 8. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
EOF
|
||||
|
||||
upload_file "$LIGHTSAIL_SERVER_IP" "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "$LIGHTSAIL_SERVER_IP" "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Lightsail instance setup completed successfully!"
|
||||
log_info "Instance: $SERVER_NAME (IP: $LIGHTSAIL_SERVER_IP)"
|
||||
echo ""
|
||||
|
||||
# 9. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$LIGHTSAIL_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
65
digitalocean/gptme.sh
Normal file
65
digitalocean/gptme.sh
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/digitalocean/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on DigitalOcean"
|
||||
echo ""
|
||||
|
||||
# 1. Resolve DigitalOcean API token
|
||||
ensure_do_token
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get droplet name and create droplet
|
||||
DROPLET_NAME=$(get_server_name)
|
||||
create_server "$DROPLET_NAME"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "$DO_SERVER_IP"
|
||||
wait_for_cloud_init "$DO_SERVER_IP"
|
||||
|
||||
# 5. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$DO_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
|
||||
# Verify installation succeeded
|
||||
if ! run_server "$DO_SERVER_IP" "command -v gptme &> /dev/null && gptme --version &> /dev/null"; then
|
||||
log_error "gptme installation verification failed"
|
||||
log_error "The 'gptme' command is not available or not working properly on server $DO_SERVER_IP"
|
||||
exit 1
|
||||
fi
|
||||
log_info "gptme installation verified successfully"
|
||||
|
||||
# 6. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 7. Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "$DO_SERVER_IP" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
|
||||
|
||||
echo ""
|
||||
log_info "DigitalOcean droplet setup completed successfully!"
|
||||
log_info "Droplet: $DROPLET_NAME (ID: $DO_DROPLET_ID, IP: $DO_SERVER_IP)"
|
||||
echo ""
|
||||
|
||||
# 9. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$DO_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
69
e2b/gptme.sh
Normal file
69
e2b/gptme.sh
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/e2b/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on E2B"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure E2B CLI and API token
|
||||
ensure_e2b_cli
|
||||
ensure_e2b_token
|
||||
|
||||
# 2. Get sandbox name and create sandbox
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Wait for base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Get model preference
|
||||
echo ""
|
||||
log_warn "Browse models at: https://openrouter.ai/models"
|
||||
log_warn "Which model would you like to use with gptme?"
|
||||
MODEL_ID=$(safe_read "Enter model ID [openrouter/auto]: ") || MODEL_ID=""
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 7. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "E2B sandbox setup completed successfully!"
|
||||
log_info "Sandbox: $SERVER_NAME (ID: $E2B_SANDBOX_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
67
fly/aider.sh
Normal file
67
fly/aider.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Aider on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Aider
|
||||
log_warn "Installing Aider..."
|
||||
run_server "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
|
||||
log_info "Aider installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1
|
||||
|
||||
# 7. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start Aider interactively
|
||||
log_warn "Starting Aider..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && aider --model openrouter/${MODEL_ID}"
|
||||
66
fly/amazonq.sh
Normal file
66
fly/amazonq.sh
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Amazon Q on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Amazon Q CLI
|
||||
log_warn "Installing Amazon Q CLI..."
|
||||
run_server "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash"
|
||||
log_info "Amazon Q CLI installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into ~/.bashrc and ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 7. Start Amazon Q interactively
|
||||
log_warn "Starting Amazon Q..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && q chat"
|
||||
118
fly/claude.sh
Normal file
118
fly/claude.sh
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Claude Code on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Claude Code
|
||||
log_warn "Installing Claude Code..."
|
||||
run_server "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
|
||||
# Verify installation
|
||||
if ! run_server "command -v claude" >/dev/null 2>&1; then
|
||||
log_error "Claude Code installation failed"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Claude Code installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
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"
|
||||
export PATH="\$HOME/.claude/local/bin:\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
# 7. Configure Claude Code settings
|
||||
log_warn "Configuring Claude Code..."
|
||||
|
||||
run_server "mkdir -p ~/.claude"
|
||||
|
||||
# Upload settings.json
|
||||
SETTINGS_TEMP=$(mktemp)
|
||||
chmod 600 "$SETTINGS_TEMP"
|
||||
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
|
||||
|
||||
upload_file "$SETTINGS_TEMP" "/root/.claude/settings.json"
|
||||
rm "$SETTINGS_TEMP"
|
||||
|
||||
# Upload ~/.claude.json global state
|
||||
GLOBAL_STATE_TEMP=$(mktemp)
|
||||
chmod 600 "$GLOBAL_STATE_TEMP"
|
||||
cat > "$GLOBAL_STATE_TEMP" << EOF
|
||||
{
|
||||
"hasCompletedOnboarding": true,
|
||||
"bypassPermissionsModeAccepted": true
|
||||
}
|
||||
EOF
|
||||
|
||||
upload_file "$GLOBAL_STATE_TEMP" "/root/.claude.json"
|
||||
rm "$GLOBAL_STATE_TEMP"
|
||||
|
||||
# Create empty CLAUDE.md
|
||||
run_server "touch ~/.claude/CLAUDE.md"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start Claude Code interactively
|
||||
log_warn "Starting Claude Code..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && claude"
|
||||
66
fly/cline.sh
Normal file
66
fly/cline.sh
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Cline on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Cline
|
||||
log_warn "Installing Cline..."
|
||||
run_server "npm install -g cline"
|
||||
log_info "Cline installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into ~/.bashrc and ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 7. Start Cline interactively
|
||||
log_warn "Starting Cline..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && cline"
|
||||
66
fly/codex.sh
Normal file
66
fly/codex.sh
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Codex CLI on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Codex CLI
|
||||
log_warn "Installing Codex CLI..."
|
||||
run_server "npm install -g @openai/codex"
|
||||
log_info "Codex CLI installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into shell config
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 7. Start Codex interactively
|
||||
log_warn "Starting Codex..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && codex"
|
||||
67
fly/gemini.sh
Normal file
67
fly/gemini.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Gemini CLI on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Gemini CLI
|
||||
log_warn "Installing Gemini CLI..."
|
||||
run_server "npm install -g @google/gemini-cli"
|
||||
log_info "Gemini CLI installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into ~/.bashrc and ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export GEMINI_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 7. Start Gemini interactively
|
||||
log_warn "Starting Gemini..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && gemini"
|
||||
71
fly/goose.sh
Normal file
71
fly/goose.sh
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Goose on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Goose
|
||||
log_warn "Installing Goose..."
|
||||
run_server "CONFIGURE=false curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash"
|
||||
|
||||
# Verify installation
|
||||
if ! run_server "command -v goose &> /dev/null && goose --version &> /dev/null"; then
|
||||
log_error "Goose installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Goose installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into shell config
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export GOOSE_PROVIDER="openrouter"
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 7. Start Goose interactively
|
||||
log_warn "Starting Goose..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && goose"
|
||||
67
fly/gptme.sh
Normal file
67
fly/gptme.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
|
||||
# 7. Inject environment variables into ~/.bashrc and ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && gptme -m openrouter/${MODEL_ID}"
|
||||
66
fly/interpreter.sh
Normal file
66
fly/interpreter.sh
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Open Interpreter on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Open Interpreter
|
||||
log_warn "Installing Open Interpreter..."
|
||||
run_server "pip install open-interpreter 2>/dev/null || pip3 install open-interpreter"
|
||||
log_info "Open Interpreter installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into shell config
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 7. Start Open Interpreter interactively
|
||||
log_warn "Starting Open Interpreter..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && interpreter"
|
||||
369
fly/lib/common.sh
Normal file
369
fly/lib/common.sh
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
#!/bin/bash
|
||||
# Common bash functions for Fly.io spawn scripts
|
||||
# Uses Fly.io Machines API + flyctl CLI for provisioning and SSH access
|
||||
|
||||
# Bash safety flags
|
||||
set -eo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Provider-agnostic functions
|
||||
# ============================================================
|
||||
|
||||
# Source shared provider-agnostic functions (local or remote fallback)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/../../shared/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/../../shared/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/shared/common.sh)"
|
||||
fi
|
||||
|
||||
# Note: Provider-agnostic functions (logging, OAuth, browser, nc_listen) are now in shared/common.sh
|
||||
|
||||
# ============================================================
|
||||
# Fly.io specific functions
|
||||
# ============================================================
|
||||
|
||||
readonly FLY_API_BASE="https://api.machines.dev/v1"
|
||||
|
||||
# Centralized curl wrapper for Fly.io Machines API
|
||||
fly_api() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local body="${3:-}"
|
||||
generic_cloud_api "$FLY_API_BASE" "$FLY_API_TOKEN" "$method" "$endpoint" "$body"
|
||||
}
|
||||
|
||||
# Ensure flyctl CLI is installed
|
||||
ensure_fly_cli() {
|
||||
if command -v fly &>/dev/null; then
|
||||
log_info "flyctl CLI available"
|
||||
return 0
|
||||
fi
|
||||
if command -v flyctl &>/dev/null; then
|
||||
log_info "flyctl CLI available (as flyctl)"
|
||||
# Create alias function so we can use 'fly' consistently
|
||||
fly() { flyctl "$@"; }
|
||||
export -f fly
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn "Installing flyctl CLI..."
|
||||
curl -L https://fly.io/install.sh | sh 2>/dev/null || {
|
||||
log_error "Failed to install flyctl CLI"
|
||||
log_error "Install manually: curl -L https://fly.io/install.sh | sh"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Add to PATH if installed to ~/.fly/bin
|
||||
if [[ -d "$HOME/.fly/bin" ]]; then
|
||||
export PATH="$HOME/.fly/bin:$PATH"
|
||||
fi
|
||||
|
||||
if ! command -v fly &>/dev/null && ! command -v flyctl &>/dev/null; then
|
||||
log_error "flyctl not found in PATH after installation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "flyctl CLI installed"
|
||||
}
|
||||
|
||||
# Ensure FLY_API_TOKEN is available (env var -> config file -> prompt+save)
|
||||
ensure_fly_token() {
|
||||
# Check Python 3 is available (required for JSON parsing)
|
||||
check_python_available || return 1
|
||||
|
||||
# 1. Check environment variable
|
||||
if [[ -n "${FLY_API_TOKEN:-}" ]]; then
|
||||
log_info "Using Fly.io API token from environment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2. Check config file
|
||||
local config_dir="$HOME/.config/spawn"
|
||||
local config_file="$config_dir/fly.json"
|
||||
if [[ -f "$config_file" ]]; then
|
||||
local saved_token=$(python3 -c "import json; print(json.load(open('$config_file')).get('token',''))" 2>/dev/null)
|
||||
if [[ -n "$saved_token" ]]; then
|
||||
export FLY_API_TOKEN="$saved_token"
|
||||
log_info "Using Fly.io API token from $config_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Try to get token from flyctl auth
|
||||
if command -v fly &>/dev/null || command -v flyctl &>/dev/null; then
|
||||
local fly_cmd="fly"
|
||||
command -v fly &>/dev/null || fly_cmd="flyctl"
|
||||
local token=$("$fly_cmd" auth token 2>/dev/null || true)
|
||||
if [[ -n "$token" ]]; then
|
||||
export FLY_API_TOKEN="$token"
|
||||
log_info "Using Fly.io API token from flyctl auth"
|
||||
# Save to config file
|
||||
mkdir -p "$config_dir"
|
||||
cat > "$config_file" << EOF
|
||||
{
|
||||
"token": "$token"
|
||||
}
|
||||
EOF
|
||||
chmod 600 "$config_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. Prompt and save
|
||||
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}"
|
||||
echo ""
|
||||
|
||||
local token=$(safe_read "Enter your Fly.io API token: ") || return 1
|
||||
if [[ -z "$token" ]]; then
|
||||
log_error "API token cannot be empty"
|
||||
log_warn "For non-interactive usage, set: FLY_API_TOKEN=your-token"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate token by making a test API call
|
||||
export FLY_API_TOKEN="$token"
|
||||
local response=$(fly_api GET "/apps?org_slug=personal")
|
||||
if echo "$response" | grep -q '"error"'; then
|
||||
log_error "Authentication failed: Invalid Fly.io API token"
|
||||
|
||||
local error_msg=$(echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('error','No details available'))" 2>/dev/null || echo "Unable to parse error")
|
||||
log_error "API Error: $error_msg"
|
||||
|
||||
log_warn "Remediation steps:"
|
||||
log_warn " 1. Run: fly tokens deploy"
|
||||
log_warn " 2. Or generate a token at: https://fly.io/dashboard"
|
||||
log_warn " 3. Ensure the token has appropriate permissions"
|
||||
unset FLY_API_TOKEN
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save to config file
|
||||
mkdir -p "$config_dir"
|
||||
cat > "$config_file" << EOF
|
||||
{
|
||||
"token": "$token"
|
||||
}
|
||||
EOF
|
||||
chmod 600 "$config_file"
|
||||
log_info "API token saved to $config_file"
|
||||
}
|
||||
|
||||
# Get the Fly.io org slug (default: personal)
|
||||
get_fly_org() {
|
||||
echo "${FLY_ORG:-personal}"
|
||||
}
|
||||
|
||||
# Get server name from env var or prompt
|
||||
get_server_name() {
|
||||
if [[ -n "${FLY_APP_NAME:-}" ]]; then
|
||||
log_info "Using app name from environment: $FLY_APP_NAME"
|
||||
if ! validate_server_name "$FLY_APP_NAME"; then
|
||||
return 1
|
||||
fi
|
||||
echo "$FLY_APP_NAME"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local server_name=$(safe_read "Enter app name: ")
|
||||
if [[ -z "$server_name" ]]; then
|
||||
log_error "App name is required"
|
||||
log_warn "Set FLY_APP_NAME environment variable for non-interactive usage:"
|
||||
log_warn " FLY_APP_NAME=dev-mk1 curl ... | bash"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! validate_server_name "$server_name"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$server_name"
|
||||
}
|
||||
|
||||
# Create a Fly.io app and machine
|
||||
create_server() {
|
||||
local name="$1"
|
||||
local region="${FLY_REGION:-iad}"
|
||||
local vm_size="${FLY_VM_SIZE:-shared-cpu-1x}"
|
||||
local vm_memory="${FLY_VM_MEMORY:-1024}"
|
||||
|
||||
# Step 1: Create the app
|
||||
log_warn "Creating Fly.io app '$name'..."
|
||||
local org=$(get_fly_org)
|
||||
local app_body="{\"app_name\":\"$name\",\"org_slug\":\"$org\"}"
|
||||
local app_response=$(fly_api POST "/apps" "$app_body")
|
||||
|
||||
if echo "$app_response" | grep -q '"error"'; then
|
||||
# App might already exist, try to continue
|
||||
local error_msg=$(echo "$app_response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('error','Unknown error'))" 2>/dev/null || echo "$app_response")
|
||||
if echo "$error_msg" | grep -qi "already exists"; then
|
||||
log_warn "App '$name' already exists, reusing it"
|
||||
else
|
||||
log_error "Failed to create Fly.io app"
|
||||
log_error "API Error: $error_msg"
|
||||
log_warn "Common issues:"
|
||||
log_warn " - App name already taken by another user"
|
||||
log_warn " - Invalid organization slug"
|
||||
log_warn " - API token lacks permissions"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_info "App '$name' created"
|
||||
fi
|
||||
|
||||
# Step 2: Create a machine in the app
|
||||
log_warn "Creating Fly.io machine (region: $region, size: $vm_size, memory: ${vm_memory}MB)..."
|
||||
|
||||
local machine_body=$(python3 -c "
|
||||
import json
|
||||
body = {
|
||||
'name': '$name',
|
||||
'region': '$region',
|
||||
'config': {
|
||||
'image': 'ubuntu:24.04',
|
||||
'guest': {
|
||||
'cpu_kind': 'shared',
|
||||
'cpus': 1,
|
||||
'memory_mb': $vm_memory
|
||||
},
|
||||
'init': {
|
||||
'exec': ['/bin/sleep', 'inf']
|
||||
},
|
||||
'auto_destroy': False
|
||||
}
|
||||
}
|
||||
print(json.dumps(body))
|
||||
")
|
||||
|
||||
local response=$(fly_api POST "/apps/$name/machines" "$machine_body")
|
||||
|
||||
if echo "$response" | grep -q '"error"'; then
|
||||
log_error "Failed to create Fly.io machine"
|
||||
|
||||
local error_msg=$(echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('error','Unknown error'))" 2>/dev/null || echo "$response")
|
||||
log_error "API Error: $error_msg"
|
||||
|
||||
log_warn "Common issues:"
|
||||
log_warn " - Insufficient account balance or payment method required"
|
||||
log_warn " - Region unavailable (try different FLY_REGION)"
|
||||
log_warn " - Machine limit reached"
|
||||
log_warn "Remediation: Check https://fly.io/dashboard"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract machine ID and state
|
||||
FLY_MACHINE_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['id'])")
|
||||
export FLY_MACHINE_ID FLY_APP_NAME="$name"
|
||||
|
||||
log_info "Machine created: ID=$FLY_MACHINE_ID, App=$name"
|
||||
|
||||
# Wait for machine to be in started state
|
||||
log_warn "Waiting for machine to start..."
|
||||
local max_attempts=30
|
||||
local attempt=1
|
||||
while [[ "$attempt" -le "$max_attempts" ]]; do
|
||||
local status_response=$(fly_api GET "/apps/$name/machines/$FLY_MACHINE_ID")
|
||||
local state=$(echo "$status_response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('state','unknown'))")
|
||||
|
||||
if [[ "$state" == "started" ]]; then
|
||||
log_info "Machine is running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn "Machine state: $state ($attempt/$max_attempts)"
|
||||
sleep 3
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "Machine did not start in time"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Wait for base tools to be installed (Fly.io uses bare Ubuntu image)
|
||||
wait_for_cloud_init() {
|
||||
log_warn "Installing base tools on Fly.io machine..."
|
||||
run_server "apt-get update -y && apt-get install -y curl unzip git zsh python3 pip" >/dev/null 2>&1 || true
|
||||
run_server "curl -fsSL https://bun.sh/install | bash" >/dev/null 2>&1 || true
|
||||
run_server 'echo "export PATH=\"\$HOME/.bun/bin:\$PATH\"" >> ~/.bashrc' >/dev/null 2>&1 || true
|
||||
run_server 'echo "export PATH=\"\$HOME/.bun/bin:\$PATH\"" >> ~/.zshrc' >/dev/null 2>&1 || true
|
||||
log_info "Base tools installed"
|
||||
}
|
||||
|
||||
# Run a command on the Fly.io machine via flyctl ssh
|
||||
run_server() {
|
||||
local cmd="$1"
|
||||
local fly_cmd="fly"
|
||||
command -v fly &>/dev/null || fly_cmd="flyctl"
|
||||
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c '$cmd'" --quiet 2>/dev/null
|
||||
}
|
||||
|
||||
# Upload a file to the machine via base64 encoding through exec
|
||||
upload_file() {
|
||||
local local_path="$1"
|
||||
local remote_path="$2"
|
||||
local content=$(base64 -w0 "$local_path" 2>/dev/null || base64 "$local_path")
|
||||
run_server "echo '$content' | base64 -d > '$remote_path'"
|
||||
}
|
||||
|
||||
# Start an interactive SSH session on the Fly.io machine
|
||||
interactive_session() {
|
||||
local cmd="$1"
|
||||
local fly_cmd="fly"
|
||||
command -v fly &>/dev/null || fly_cmd="flyctl"
|
||||
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c '$cmd'"
|
||||
}
|
||||
|
||||
# Destroy a Fly.io machine and app
|
||||
destroy_server() {
|
||||
local app_name="${1:-$FLY_APP_NAME}"
|
||||
|
||||
log_warn "Destroying Fly.io app and machines for '$app_name'..."
|
||||
|
||||
# List and destroy all machines in the app
|
||||
local machines=$(fly_api GET "/apps/$app_name/machines")
|
||||
local machine_ids=$(echo "$machines" | python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if isinstance(data, list):
|
||||
for m in data:
|
||||
print(m['id'])
|
||||
" 2>/dev/null || true)
|
||||
|
||||
for mid in $machine_ids; do
|
||||
log_warn "Stopping machine $mid..."
|
||||
fly_api POST "/apps/$app_name/machines/$mid/stop" '{}' >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
log_warn "Destroying machine $mid..."
|
||||
fly_api DELETE "/apps/$app_name/machines/$mid?force=true" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
# Delete the app
|
||||
fly_api DELETE "/apps/$app_name" >/dev/null 2>&1 || true
|
||||
log_info "App '$app_name' destroyed"
|
||||
}
|
||||
|
||||
# List all Fly.io apps and machines
|
||||
list_servers() {
|
||||
local org=$(get_fly_org)
|
||||
local response=$(fly_api GET "/apps?org_slug=$org")
|
||||
|
||||
python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
apps = data if isinstance(data, list) else data.get('apps', [])
|
||||
if not apps:
|
||||
print('No apps found')
|
||||
sys.exit(0)
|
||||
print(f\"{'NAME':<25} {'ID':<20} {'STATUS':<12} {'NETWORK':<20}\")
|
||||
print('-' * 77)
|
||||
for a in apps:
|
||||
name = a.get('name', 'N/A')
|
||||
aid = a.get('id', 'N/A')
|
||||
status = a.get('status', 'N/A')
|
||||
network = a.get('network', 'N/A')
|
||||
print(f'{name:<25} {aid:<20} {status:<12} {network:<20}')
|
||||
" <<< "$response"
|
||||
}
|
||||
81
fly/nanoclaw.sh
Normal file
81
fly/nanoclaw.sh
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "NanoClaw on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install tsx and clone nanoclaw
|
||||
log_warn "Installing tsx..."
|
||||
run_server "source ~/.bashrc && bun install -g tsx"
|
||||
|
||||
log_warn "Cloning and building nanoclaw..."
|
||||
run_server "git clone https://github.com/gavrielc/nanoclaw.git ~/nanoclaw && cd ~/nanoclaw && npm install && npm run build"
|
||||
log_info "NanoClaw installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Inject environment variables into shell config
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
# 7. Create nanoclaw .env file
|
||||
log_warn "Configuring nanoclaw..."
|
||||
|
||||
DOTENV_TEMP=$(mktemp)
|
||||
chmod 600 "$DOTENV_TEMP"
|
||||
cat > "$DOTENV_TEMP" << EOF
|
||||
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
|
||||
EOF
|
||||
|
||||
upload_file "$DOTENV_TEMP" "/root/nanoclaw/.env"
|
||||
rm "$DOTENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start nanoclaw
|
||||
log_warn "Starting nanoclaw..."
|
||||
log_warn "You will need to scan a WhatsApp QR code to authenticate."
|
||||
echo ""
|
||||
interactive_session "cd ~/nanoclaw && source ~/.zshrc && npm run dev"
|
||||
103
fly/openclaw.sh
Normal file
103
fly/openclaw.sh
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/fly/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "OpenClaw on Fly.io"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure flyctl CLI and API token
|
||||
ensure_fly_cli
|
||||
ensure_fly_token
|
||||
|
||||
# 2. Get app name and create machine
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install openclaw via bun
|
||||
log_warn "Installing openclaw..."
|
||||
run_server "source ~/.bashrc && bun install -g openclaw"
|
||||
log_info "OpenClaw installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Openclaw") || exit 1
|
||||
|
||||
# 6. Inject environment variables into shell config
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
chmod 600 "$ENV_TEMP"
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_API_KEY="${OPENROUTER_API_KEY}"
|
||||
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
||||
export PATH="\$HOME/.bun/bin:\$PATH"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
# 7. Configure openclaw
|
||||
log_warn "Configuring openclaw..."
|
||||
|
||||
run_server "rm -rf ~/.openclaw && mkdir -p ~/.openclaw"
|
||||
|
||||
# Generate a random gateway token
|
||||
GATEWAY_TOKEN=$(openssl rand -hex 16)
|
||||
|
||||
OPENCLAW_CONFIG_TEMP=$(mktemp)
|
||||
chmod 600 "$OPENCLAW_CONFIG_TEMP"
|
||||
cat > "$OPENCLAW_CONFIG_TEMP" << EOF
|
||||
{
|
||||
"env": {
|
||||
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}"
|
||||
},
|
||||
"gateway": {
|
||||
"mode": "local",
|
||||
"auth": {
|
||||
"token": "${GATEWAY_TOKEN}"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": {
|
||||
"primary": "openrouter/${MODEL_ID}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
upload_file "$OPENCLAW_CONFIG_TEMP" "/root/.openclaw/openclaw.json"
|
||||
rm "$OPENCLAW_CONFIG_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Fly.io machine setup completed successfully!"
|
||||
log_info "App: $SERVER_NAME (Machine ID: $FLY_MACHINE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start openclaw gateway in background and launch TUI
|
||||
log_warn "Starting openclaw..."
|
||||
run_server "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
sleep 2
|
||||
interactive_session "source ~/.zshrc && openclaw tui"
|
||||
72
gcp/gptme.sh
Normal file
72
gcp/gptme.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/gcp/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on GCP Compute Engine"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure gcloud is configured
|
||||
ensure_gcloud
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get server name and create server
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "$GCP_SERVER_IP"
|
||||
wait_for_cloud_init "$GCP_SERVER_IP"
|
||||
|
||||
# 5. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$GCP_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
# 6. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 7. Get model preference
|
||||
echo ""
|
||||
log_warn "Browse models at: https://openrouter.ai/models"
|
||||
log_warn "Which model would you like to use with gptme?"
|
||||
MODEL_ID=$(safe_read "Enter model ID [openrouter/auto]: ") || MODEL_ID=""
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 8. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
EOF
|
||||
|
||||
upload_file "$GCP_SERVER_IP" "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "$GCP_SERVER_IP" "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "GCP instance setup completed successfully!"
|
||||
log_info "Instance: $GCP_INSTANCE_NAME_ACTUAL (Zone: $GCP_ZONE, IP: $GCP_SERVER_IP)"
|
||||
echo ""
|
||||
|
||||
# 9. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$GCP_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
65
hetzner/gptme.sh
Normal file
65
hetzner/gptme.sh
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/hetzner/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Hetzner Cloud"
|
||||
echo ""
|
||||
|
||||
# 1. Resolve Hetzner API token
|
||||
ensure_hcloud_token
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get server name and create server
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "$HETZNER_SERVER_IP"
|
||||
wait_for_cloud_init "$HETZNER_SERVER_IP"
|
||||
|
||||
# 5. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$HETZNER_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
|
||||
# Verify installation succeeded
|
||||
if ! run_server "$HETZNER_SERVER_IP" "command -v gptme &> /dev/null && gptme --version &> /dev/null"; then
|
||||
log_error "gptme installation verification failed"
|
||||
log_error "The 'gptme' command is not available or not working properly on server $HETZNER_SERVER_IP"
|
||||
exit 1
|
||||
fi
|
||||
log_info "gptme installation verified successfully"
|
||||
|
||||
# 6. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "$HETZNER_SERVER_IP" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
|
||||
|
||||
echo ""
|
||||
log_info "Hetzner server setup completed successfully!"
|
||||
log_info "Server: $SERVER_NAME (ID: $HETZNER_SERVER_ID, IP: $HETZNER_SERVER_IP)"
|
||||
echo ""
|
||||
|
||||
# 9. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$HETZNER_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
72
lambda/gptme.sh
Normal file
72
lambda/gptme.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/lambda/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Lambda Cloud"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Lambda API key is configured
|
||||
ensure_lambda_token
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get instance name and create server
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "$LAMBDA_SERVER_IP"
|
||||
wait_for_cloud_init "$LAMBDA_SERVER_IP"
|
||||
|
||||
# 5. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$LAMBDA_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
# 6. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 7. Get model preference
|
||||
echo ""
|
||||
log_warn "Browse models at: https://openrouter.ai/models"
|
||||
log_warn "Which model would you like to use with gptme?"
|
||||
MODEL_ID=$(safe_read "Enter model ID [openrouter/auto]: ") || MODEL_ID=""
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 8. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
EOF
|
||||
|
||||
upload_file "$LAMBDA_SERVER_IP" "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "$LAMBDA_SERVER_IP" "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Lambda Cloud instance setup completed successfully!"
|
||||
log_info "Instance: $SERVER_NAME (IP: $LAMBDA_SERVER_IP)"
|
||||
echo ""
|
||||
|
||||
# 9. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$LAMBDA_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
30
linode/gptme.sh
Normal file
30
linode/gptme.sh
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
|
||||
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi
|
||||
log_info "gptme on Linode"
|
||||
echo ""
|
||||
ensure_linode_token
|
||||
ensure_ssh_key
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
verify_server_connectivity "$LINODE_SERVER_IP"
|
||||
wait_for_cloud_init "$LINODE_SERVER_IP"
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$LINODE_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then log_info "Using OpenRouter API key from environment"
|
||||
else OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180); fi
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "$LINODE_SERVER_IP" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
|
||||
echo ""
|
||||
log_info "Linode setup completed successfully!"
|
||||
echo ""
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$LINODE_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
|
|
@ -161,6 +161,23 @@
|
|||
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}"
|
||||
},
|
||||
"notes": "Works with OpenRouter via OPENAI_BASE_URL override"
|
||||
},
|
||||
"gptme": {
|
||||
"name": "gptme",
|
||||
"description": "Personal AI agent in the terminal with tools for code, terminal, browser, and more",
|
||||
"url": "https://github.com/gptme/gptme",
|
||||
"install": "pip install gptme",
|
||||
"launch": "gptme -m openrouter/${MODEL_ID}",
|
||||
"env": {
|
||||
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}"
|
||||
},
|
||||
"interactive_prompts": {
|
||||
"model_id": {
|
||||
"prompt": "Enter model ID",
|
||||
"default": "openrouter/auto"
|
||||
}
|
||||
},
|
||||
"notes": "Natively supports OpenRouter via OPENROUTER_API_KEY and -m openrouter/... flag"
|
||||
}
|
||||
},
|
||||
"clouds": {
|
||||
|
|
@ -308,6 +325,23 @@
|
|||
"image": "debian_slim"
|
||||
},
|
||||
"notes": "No SSH — uses Modal Python SDK for exec. Sub-second cold starts. Requires pip install modal."
|
||||
},
|
||||
"fly": {
|
||||
"name": "Fly.io",
|
||||
"description": "Fly.io Machines via REST API and flyctl CLI",
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"matrix": {
|
||||
|
|
@ -410,6 +444,27 @@
|
|||
"modal/interpreter": "implemented",
|
||||
"modal/gemini": "implemented",
|
||||
"modal/amazonq": "implemented",
|
||||
"modal/cline": "implemented"
|
||||
"modal/cline": "implemented",
|
||||
"sprite/gptme": "implemented",
|
||||
"hetzner/gptme": "implemented",
|
||||
"digitalocean/gptme": "implemented",
|
||||
"vultr/gptme": "implemented",
|
||||
"linode/gptme": "implemented",
|
||||
"lambda/gptme": "implemented",
|
||||
"aws-lightsail/gptme": "implemented",
|
||||
"gcp/gptme": "implemented",
|
||||
"e2b/gptme": "implemented",
|
||||
"modal/gptme": "implemented",
|
||||
"fly/claude": "implemented",
|
||||
"fly/aider": "implemented",
|
||||
"fly/openclaw": "implemented",
|
||||
"fly/nanoclaw": "implemented",
|
||||
"fly/goose": "implemented",
|
||||
"fly/codex": "implemented",
|
||||
"fly/interpreter": "implemented",
|
||||
"fly/gemini": "implemented",
|
||||
"fly/amazonq": "implemented",
|
||||
"fly/cline": "implemented",
|
||||
"fly/gptme": "implemented"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
modal/gptme.sh
Normal file
68
modal/gptme.sh
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/modal/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Modal"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Modal CLI
|
||||
ensure_modal_cli
|
||||
|
||||
# 2. Get sandbox name and create sandbox
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Wait for base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_server "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
# 5. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# 6. Get model preference
|
||||
echo ""
|
||||
log_warn "Browse models at: https://openrouter.ai/models"
|
||||
log_warn "Which model would you like to use with gptme?"
|
||||
MODEL_ID=$(safe_read "Enter model ID [openrouter/auto]: ") || MODEL_ID=""
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 7. Inject environment variables into ~/.zshrc
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
ENV_TEMP=$(mktemp)
|
||||
cat > "$ENV_TEMP" << EOF
|
||||
|
||||
# [spawn:env]
|
||||
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}"
|
||||
EOF
|
||||
|
||||
upload_file "$ENV_TEMP" "/tmp/env_config"
|
||||
run_server "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
|
||||
rm "$ENV_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Modal sandbox setup completed successfully!"
|
||||
log_info "Sandbox: $SERVER_NAME (ID: $MODAL_SANDBOX_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
63
sprite/gptme.sh
Normal file
63
sprite/gptme.sh
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/sprite/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Sprite"
|
||||
echo ""
|
||||
|
||||
# Setup sprite environment
|
||||
ensure_sprite_installed
|
||||
ensure_sprite_authenticated
|
||||
|
||||
SPRITE_NAME=$(get_sprite_name)
|
||||
ensure_sprite_exists "$SPRITE_NAME" 5
|
||||
verify_sprite_connectivity "$SPRITE_NAME"
|
||||
|
||||
log_warn "Setting up sprite environment..."
|
||||
|
||||
# Configure shell environment
|
||||
setup_shell_environment "$SPRITE_NAME"
|
||||
|
||||
# Install gptme
|
||||
log_warn "Installing gptme..."
|
||||
run_sprite "$SPRITE_NAME" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
|
||||
# Verify installation succeeded
|
||||
if ! run_sprite "$SPRITE_NAME" "command -v gptme &> /dev/null && gptme --version &> /dev/null"; then
|
||||
log_error "gptme installation verification failed"
|
||||
log_error "The 'gptme' command is not available or not working properly"
|
||||
exit 1
|
||||
fi
|
||||
log_info "gptme installation verified successfully"
|
||||
|
||||
# Get OpenRouter API key via OAuth
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_sprite "$SPRITE_NAME" \
|
||||
"OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
|
||||
|
||||
echo ""
|
||||
log_info "Sprite setup completed successfully!"
|
||||
echo ""
|
||||
|
||||
# Start gptme interactively
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
sprite exec -s "$SPRITE_NAME" -tty -- zsh -c "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
47
vultr/gptme.sh
Normal file
47
vultr/gptme.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/vultr/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Vultr"
|
||||
echo ""
|
||||
|
||||
ensure_vultr_token
|
||||
ensure_ssh_key
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
verify_server_connectivity "$VULTR_SERVER_IP"
|
||||
wait_for_cloud_init "$VULTR_SERVER_IP"
|
||||
|
||||
log_warn "Installing gptme..."
|
||||
run_server "$VULTR_SERVER_IP" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "$VULTR_SERVER_IP" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
|
||||
|
||||
echo ""
|
||||
log_info "Vultr instance setup completed successfully!"
|
||||
log_info "Server: $SERVER_NAME (ID: $VULTR_SERVER_ID, IP: $VULTR_SERVER_IP)"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "$VULTR_SERVER_IP" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue