feat: Add Latitude.sh cloud provider with all 13 agents (#85)

Add Latitude.sh as the 19th cloud provider in the spawn matrix.
Latitude.sh offers bare metal servers and VMs via REST API with
hourly billing, global locations, and plans starting at $0.07/hr.

New files:
- latitude/lib/common.sh: Provider functions (API wrapper, server
  creation/deletion, SSH key management, wait-for-ready)
- latitude/{agent}.sh: All 13 agent deployment scripts
- latitude/README.md: Usage docs with env vars and pricing

Updated:
- manifest.json: Added latitude cloud + 13 matrix entries
- README.md: Updated matrix table (19 clouds, 247 combinations)

Agent: cloud-scout

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-09 08:33:47 -08:00 committed by GitHub
parent 95a629ef36
commit 89ed02fe55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1363 additions and 17 deletions

View file

@ -2,7 +2,7 @@
Launch any AI coding agent on any cloud with a single command. All models powered by [OpenRouter](https://openrouter.ai). (ALPHA software, use at your own risk!)
**13 agents. 18 clouds. 234 combinations. Zero config.**
**13 agents. 19 clouds. 247 combinations. Zero config.**
## Install
@ -86,21 +86,21 @@ For cloud-specific auth, see each cloud's README in this repository.
## Matrix
| | [Sprite](sprite/) | [Hetzner](hetzner/) | [DigitalOcean](digitalocean/) | [Vultr](vultr/) | [Linode](linode/) | [Lambda](lambda/) | [Lightsail](aws-lightsail/) | [GCP](gcp/) | [E2B](e2b/) | [Modal](modal/) | [Fly.io](fly/) | [Civo](civo/) | [Scaleway](scaleway/) | [Daytona](daytona/) | [RunPod](runpod/) | [UpCloud](upcloud/) | [BinaryLane](binarylane/) | [Genesis Cloud](genesiscloud/) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| [**Claude Code**](https://claude.ai) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenClaw**](https://github.com/OpenRouterTeam/openclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**NanoClaw**](https://github.com/gavrielc/nanoclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Aider**](https://github.com/paul-gauthier/aider) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Goose**](https://github.com/block/goose) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Codex CLI**](https://github.com/openai/codex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Open Interpreter**](https://github.com/OpenInterpreter/open-interpreter) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Gemini CLI**](https://github.com/google-gemini/gemini-cli) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Amazon Q CLI**](https://aws.amazon.com/q/developer/) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Cline**](https://github.com/cline/cline) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**gptme**](https://github.com/gptme/gptme) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenCode**](https://github.com/opencode-ai/opencode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Plandex**](https://github.com/plandex-ai/plandex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| | [Sprite](sprite/) | [Hetzner](hetzner/) | [DigitalOcean](digitalocean/) | [Vultr](vultr/) | [Linode](linode/) | [Lambda](lambda/) | [Lightsail](aws-lightsail/) | [GCP](gcp/) | [E2B](e2b/) | [Modal](modal/) | [Fly.io](fly/) | [Civo](civo/) | [Scaleway](scaleway/) | [Daytona](daytona/) | [RunPod](runpod/) | [UpCloud](upcloud/) | [BinaryLane](binarylane/) | [Genesis Cloud](genesiscloud/) | [Latitude.sh](latitude/) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| [**Claude Code**](https://claude.ai) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenClaw**](https://github.com/OpenRouterTeam/openclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**NanoClaw**](https://github.com/gavrielc/nanoclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Aider**](https://github.com/paul-gauthier/aider) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Goose**](https://github.com/block/goose) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Codex CLI**](https://github.com/openai/codex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Open Interpreter**](https://github.com/OpenInterpreter/open-interpreter) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Gemini CLI**](https://github.com/google-gemini/gemini-cli) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Amazon Q CLI**](https://aws.amazon.com/q/developer/) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Cline**](https://github.com/cline/cline) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**gptme**](https://github.com/gptme/gptme) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenCode**](https://github.com/opencode-ai/opencode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Plandex**](https://github.com/plandex-ai/plandex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
### How it works

119
latitude/README.md Normal file
View file

@ -0,0 +1,119 @@
# Latitude.sh
Bare metal and VM cloud servers via REST API. [Latitude.sh](https://www.latitude.sh/)
## Agents
#### Claude Code
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/claude.sh)
```
#### OpenClaw
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/openclaw.sh)
```
#### NanoClaw
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/nanoclaw.sh)
```
#### Aider
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/aider.sh)
```
#### Goose
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/goose.sh)
```
#### Codex CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/codex.sh)
```
#### Open Interpreter
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/interpreter.sh)
```
#### Gemini CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/gemini.sh)
```
#### Amazon Q CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/amazonq.sh)
```
#### Cline
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/cline.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/gptme.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/plandex.sh)
```
## Non-Interactive Mode
```bash
LATITUDE_SERVER_NAME=dev-mk1 \
LATITUDE_API_KEY=your-api-key \
OPENROUTER_API_KEY=sk-or-v1-xxxxx \
bash <(curl -fsSL https://openrouter.ai/lab/spawn/latitude/claude.sh)
```
## Environment Variables
| Variable | Description |
|----------|-------------|
| `LATITUDE_API_KEY` | Latitude.sh API key (required) |
| `LATITUDE_SERVER_NAME` | Server hostname (prompted if not set) |
| `LATITUDE_PROJECT_ID` | Project ID (auto-detected from first project) |
| `LATITUDE_PLAN` | Server plan (default: `vm.tiny`) |
| `LATITUDE_SITE` | Data center site (default: `DAL2`) |
| `LATITUDE_OS` | Operating system (default: `ubuntu_24_04_x64_lts`) |
| `OPENROUTER_API_KEY` | OpenRouter API key for agent access |
## Available Plans
| Plan | Specs | Price |
|------|-------|-------|
| `vm.tiny` | 4 vCPUs, 8GB RAM | $0.07/hr |
| `vm.small` | 8 vCPUs, 16GB RAM | $0.14/hr |
| `vm.medium` | 12 vCPUs, 24GB RAM | $0.25/hr |
| `m4.metal.small` | AMD 4244P (6 cores), 64GB RAM | $0.37/hr |
## Available Sites
US (Dallas, LAX, NYC, Chicago, Ashburn, Miami, Silicon Valley), Brazil, Australia, Chile, Japan, Mexico, UK, Germany, Argentina, Colombia, Singapore, Netherlands.
Get your API key at: https://www.latitude.sh/dashboard (Settings & Billing -> API Keys)

69
latitude/aider.sh Normal file
View file

@ -0,0 +1,69 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Aider on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and Aider
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Aider..."
run_server "${LATITUDE_SERVER_IP}" "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
# Verify installation succeeded
if ! run_server "${LATITUDE_SERVER_IP}" "command -v aider &> /dev/null && aider --version &> /dev/null"; then
log_error "Aider installation verification failed"
log_error "The 'aider' command is not available or not working properly on server ${LATITUDE_SERVER_IP}"
exit 1
fi
log_info "Aider installation verified successfully"
# 7. 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" "Aider") || exit 1
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 8. Start Aider interactively
log_warn "Starting Aider..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && aider --model openrouter/${MODEL_ID}"

48
latitude/amazonq.sh Normal file
View file

@ -0,0 +1,48 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Amazon Q on Latitude.sh"
echo ""
ensure_latitude_token
ensure_ssh_key
SERVER_NAME=$(get_server_name)
create_server "${SERVER_NAME}"
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
verify_server_connectivity "${LATITUDE_SERVER_IP}"
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Amazon Q CLI..."
run_server "${LATITUDE_SERVER_IP}" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash"
log_info "Amazon Q CLI 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
log_warn "Starting Amazon Q..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && q chat"

76
latitude/claude.sh Normal file
View file

@ -0,0 +1,76 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Claude Code on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and Claude Code
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Claude Code..."
run_server "${LATITUDE_SERVER_IP}" "curl -fsSL https://claude.ai/install.sh | bash"
# Verify installation succeeded
if ! run_server "${LATITUDE_SERVER_IP}" "command -v claude &> /dev/null && claude --version &> /dev/null"; then
log_error "Claude Code installation verification failed"
log_error "The 'claude' command is not available or not working properly on server ${LATITUDE_SERVER_IP}"
exit 1
fi
log_info "Claude Code installation verified successfully"
# 7. 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
"ANTHROPIC_AUTH_TOKEN=${OPENROUTER_API_KEY}" \
"ANTHROPIC_API_KEY=" \
"CLAUDE_CODE_SKIP_ONBOARDING=1" \
"CLAUDE_CODE_ENABLE_TELEMETRY=0"
# 8. Configure Claude Code settings
setup_claude_code_config "${OPENROUTER_API_KEY}" \
"upload_file ${LATITUDE_SERVER_IP}" \
"run_server ${LATITUDE_SERVER_IP}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 9. Start Claude Code interactively
log_warn "Starting Claude Code..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && claude"

48
latitude/cline.sh Normal file
View file

@ -0,0 +1,48 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Cline on Latitude.sh"
echo ""
ensure_latitude_token
ensure_ssh_key
SERVER_NAME=$(get_server_name)
create_server "${SERVER_NAME}"
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
verify_server_connectivity "${LATITUDE_SERVER_IP}"
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Cline..."
run_server "${LATITUDE_SERVER_IP}" "npm install -g cline"
log_info "Cline 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
log_warn "Starting Cline..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && cline"

48
latitude/codex.sh Normal file
View file

@ -0,0 +1,48 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Codex CLI on Latitude.sh"
echo ""
ensure_latitude_token
ensure_ssh_key
SERVER_NAME=$(get_server_name)
create_server "${SERVER_NAME}"
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
verify_server_connectivity "${LATITUDE_SERVER_IP}"
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Codex CLI..."
run_server "${LATITUDE_SERVER_IP}" "npm install -g @openai/codex"
log_info "Codex CLI 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
log_warn "Starting Codex..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && codex"

49
latitude/gemini.sh Normal file
View file

@ -0,0 +1,49 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Gemini CLI on Latitude.sh"
echo ""
ensure_latitude_token
ensure_ssh_key
SERVER_NAME=$(get_server_name)
create_server "${SERVER_NAME}"
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
verify_server_connectivity "${LATITUDE_SERVER_IP}"
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Gemini CLI..."
run_server "${LATITUDE_SERVER_IP}" "npm install -g @google/gemini-cli"
log_info "Gemini CLI 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"GEMINI_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
log_warn "Starting Gemini..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && gemini"

67
latitude/goose.sh Normal file
View 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/latitude/lib/common.sh)"
fi
log_info "Goose on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and Goose
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Goose..."
run_server "${LATITUDE_SERVER_IP}" "CONFIGURE=false curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash"
# Verify installation succeeded
if ! run_server "${LATITUDE_SERVER_IP}" "command -v goose &> /dev/null && goose --version &> /dev/null"; then
log_error "Goose installation verification failed"
log_error "The 'goose' command is not available or not working properly on server ${LATITUDE_SERVER_IP}"
exit 1
fi
log_info "Goose installation verified successfully"
# 7. 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"GOOSE_PROVIDER=openrouter" \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 8. Start Goose interactively
log_warn "Starting Goose..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && goose"

69
latitude/gptme.sh Normal file
View file

@ -0,0 +1,69 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "gptme on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and gptme
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing gptme..."
run_server "${LATITUDE_SERVER_IP}" "pip install gptme 2>/dev/null || pip3 install gptme"
# Verify installation succeeded
if ! run_server "${LATITUDE_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 ${LATITUDE_SERVER_IP}"
exit 1
fi
log_info "gptme installation verified successfully"
# 7. 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 "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 8. Start gptme interactively
log_warn "Starting gptme..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"

48
latitude/interpreter.sh Normal file
View file

@ -0,0 +1,48 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "Open Interpreter on Latitude.sh"
echo ""
ensure_latitude_token
ensure_ssh_key
SERVER_NAME=$(get_server_name)
create_server "${SERVER_NAME}"
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
verify_server_connectivity "${LATITUDE_SERVER_IP}"
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Open Interpreter..."
run_server "${LATITUDE_SERVER_IP}" "pip install open-interpreter 2>/dev/null || pip3 install open-interpreter"
log_info "Open Interpreter 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
log_warn "Starting Open Interpreter..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && interpreter"

419
latitude/lib/common.sh Normal file
View file

@ -0,0 +1,419 @@
#!/bin/bash
set -eo pipefail
# Common bash functions for Latitude.sh spawn scripts
# ============================================================
# 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
# ============================================================
# Latitude.sh specific functions
# ============================================================
readonly LATITUDE_API_BASE="https://api.latitude.sh"
# Centralized curl wrapper for Latitude.sh API
latitude_api() {
local method="$1"
local endpoint="$2"
local body="${3:-}"
# shellcheck disable=SC2154
generic_cloud_api "$LATITUDE_API_BASE" "$LATITUDE_API_KEY" "$method" "$endpoint" "$body"
}
# Test Latitude.sh API token validity
test_latitude_token() {
local response
response=$(latitude_api GET "/projects")
if echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if 'data' in d else 1)" 2>/dev/null; then
return 0
fi
local error_msg
error_msg=$(echo "$response" | python3 -c "
import json,sys
try:
d=json.loads(sys.stdin.read())
errors = d.get('errors', d.get('error', {}))
if isinstance(errors, list) and errors:
print(errors[0].get('detail', errors[0].get('title', 'Unknown error')))
elif isinstance(errors, dict):
print(errors.get('detail', errors.get('message', 'Unknown error')))
else:
print('Unknown error')
except: print('Unable to parse error')
" 2>/dev/null || echo "Unable to parse error")
log_error "API Error: $error_msg"
log_error ""
log_error "How to fix:"
log_error " 1. Verify your API key at: https://www.latitude.sh/dashboard → Settings & Billing → API Keys"
log_error " 2. Ensure the API key has not expired"
log_error " 3. Check that you have an active project"
return 1
}
# Ensure LATITUDE_API_KEY is available (env var -> config file -> prompt+save)
ensure_latitude_token() {
ensure_api_token_with_provider \
"Latitude.sh" \
"LATITUDE_API_KEY" \
"$HOME/.config/spawn/latitude.json" \
"https://www.latitude.sh/dashboard → Settings & Billing → API Keys" \
"test_latitude_token"
}
# Get the default project ID from the Latitude.sh account
get_latitude_project_id() {
if [[ -n "${LATITUDE_PROJECT_ID:-}" ]]; then
echo "$LATITUDE_PROJECT_ID"
return 0
fi
local response
response=$(latitude_api GET "/projects")
local project_id
project_id=$(echo "$response" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
projects = data.get('data', [])
if not projects:
sys.exit(1)
# Use first project
print(projects[0]['id'])
" 2>/dev/null)
if [[ -z "$project_id" ]]; then
log_error "No projects found in your Latitude.sh account"
log_error "Create a project at: https://www.latitude.sh/dashboard"
return 1
fi
LATITUDE_PROJECT_ID="$project_id"
export LATITUDE_PROJECT_ID
log_info "Using Latitude.sh project: $project_id"
echo "$project_id"
}
# Check if SSH key is registered with Latitude.sh
latitude_check_ssh_key() {
local fingerprint="$1"
local existing_keys
existing_keys=$(latitude_api GET "/ssh_keys")
echo "$existing_keys" | grep -q "$fingerprint"
}
# Register SSH key with Latitude.sh
latitude_register_ssh_key() {
local key_name="$1"
local pub_path="$2"
local pub_key
pub_key=$(cat "$pub_path")
local json_pub_key
json_pub_key=$(json_escape "$pub_key")
local body
body=$(python3 -c "
import json
body = {
'data': {
'type': 'ssh_keys',
'attributes': {
'name': '$key_name',
'public_key': json.loads($json_pub_key)
}
}
}
print(json.dumps(body))
")
local response
response=$(latitude_api POST "/ssh_keys" "$body")
if echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if 'data' in d else 1)" 2>/dev/null; then
return 0
fi
local error_msg
error_msg=$(echo "$response" | python3 -c "
import json,sys
try:
d=json.loads(sys.stdin.read())
errors = d.get('errors', [])
if isinstance(errors, list) and errors:
print(errors[0].get('detail', errors[0].get('title', 'Unknown error')))
else:
print('Unknown error')
except: print(sys.stdin.read())
" 2>/dev/null || echo "$response")
log_error "API Error: $error_msg"
log_error ""
log_error "Common causes:"
log_error " - SSH key already registered with this name"
log_error " - Invalid SSH key format (must be valid ed25519 public key)"
log_error " - API key lacks write permissions"
return 1
}
# Ensure SSH key exists locally and is registered with Latitude.sh
ensure_ssh_key() {
ensure_ssh_key_with_provider latitude_check_ssh_key latitude_register_ssh_key "Latitude.sh"
}
# Get server name from env var or prompt
get_server_name() {
local server_name
server_name=$(get_resource_name "LATITUDE_SERVER_NAME" "Enter server name: ") || return 1
if ! validate_server_name "$server_name"; then
return 1
fi
echo "$server_name"
}
# Create a Latitude.sh server
create_server() {
local hostname="$1"
local plan="${LATITUDE_PLAN:-vm.tiny}"
local site="${LATITUDE_SITE:-DAL2}"
local os="${LATITUDE_OS:-ubuntu_24_04_x64_lts}"
log_warn "Creating Latitude.sh server '$hostname' (plan: $plan, site: $site)..."
# Get project ID
local project_id
project_id=$(get_latitude_project_id) || return 1
# Get all SSH key IDs
local ssh_keys_response
ssh_keys_response=$(latitude_api GET "/ssh_keys")
local ssh_key_ids
ssh_key_ids=$(echo "$ssh_keys_response" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
ids = [k['id'] for k in data.get('data', [])]
print(json.dumps(ids))
" 2>/dev/null || echo "[]")
local body
body=$(python3 -c "
import json
body = {
'data': {
'type': 'servers',
'attributes': {
'hostname': '$hostname',
'plan': '$plan',
'site': '$site',
'operating_system': '$os',
'project': '$project_id',
'ssh_keys': $ssh_key_ids
}
}
}
print(json.dumps(body))
")
local response
response=$(latitude_api POST "/servers" "$body")
# Check for errors
if ! echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if 'data' in d else 1)" 2>/dev/null; then
log_error "Failed to create Latitude.sh server"
local error_msg
error_msg=$(echo "$response" | python3 -c "
import json,sys
try:
d=json.loads(sys.stdin.read())
errors = d.get('errors', [])
if isinstance(errors, list) and errors:
print(errors[0].get('detail', errors[0].get('title', 'Unknown error')))
else:
print('Unknown error')
except: print(sys.stdin.read())
" 2>/dev/null || echo "$response")
log_error "API Error: $error_msg"
log_error ""
log_error "Common issues:"
log_error " - Insufficient account balance or payment method required"
log_error " - Plan/site unavailable (try different LATITUDE_PLAN or LATITUDE_SITE)"
log_error " - Server limit reached for your account"
log_error ""
log_error "Check your account status: https://www.latitude.sh/dashboard"
return 1
fi
# Extract server ID
LATITUDE_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['data']['id'])")
export LATITUDE_SERVER_ID
log_info "Server created: ID=$LATITUDE_SERVER_ID"
log_warn "Waiting for server provisioning (this may take a few minutes for bare metal)..."
}
# Wait for server to become active and get its IP address
wait_for_server_ready() {
local server_id="$1"
local max_attempts=${2:-60}
local attempt=1
log_warn "Waiting for server $server_id to become active..."
while [[ "$attempt" -le "$max_attempts" ]]; do
local response
response=$(latitude_api GET "/servers/$server_id")
local status
status=$(echo "$response" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
server = data.get('data', {})
attrs = server.get('attributes', {})
print(attrs.get('status', 'unknown'))
" 2>/dev/null || echo "unknown")
if [[ "$status" == "on" ]] || [[ "$status" == "active" ]]; then
# Extract IP address
LATITUDE_SERVER_IP=$(echo "$response" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
server = data.get('data', {})
attrs = server.get('attributes', {})
# Check for IP in network attributes
network = attrs.get('network', {})
if isinstance(network, dict):
ip = network.get('ip', '')
if ip:
print(ip)
sys.exit(0)
# Check for IP in relationships or included data
ips = attrs.get('ip_addresses', [])
if isinstance(ips, list):
for ip_obj in ips:
if isinstance(ip_obj, dict):
addr = ip_obj.get('address', '')
if addr and ':' not in addr: # Skip IPv6
print(addr)
sys.exit(0)
elif isinstance(ip_obj, str) and ':' not in ip_obj:
print(ip_obj)
sys.exit(0)
# Fallback: try primary_ipv4
primary = attrs.get('primary_ipv4', '')
if primary:
print(primary)
sys.exit(0)
sys.exit(1)
" 2>/dev/null)
if [[ -n "$LATITUDE_SERVER_IP" ]]; then
export LATITUDE_SERVER_IP
log_info "Server active: IP=$LATITUDE_SERVER_IP"
return 0
fi
# IP might not be assigned yet, keep waiting
log_warn "Server active but IP not yet assigned... (attempt $attempt/$max_attempts)"
else
log_warn "Server status: $status (attempt $attempt/$max_attempts)"
fi
sleep 10
attempt=$((attempt + 1))
done
log_error "Server failed to become active after $max_attempts attempts"
return 1
}
# Wait for SSH connectivity
verify_server_connectivity() {
local ip="$1"
local max_attempts=${2:-30}
# shellcheck disable=SC2154
generic_ssh_wait "root" "$ip" "$SSH_OPTS -o ConnectTimeout=5" "echo ok" "SSH connectivity" "$max_attempts" 5
}
# Run a command on the server
run_server() {
local ip="$1"
local cmd="$2"
# shellcheck disable=SC2086
ssh $SSH_OPTS "root@$ip" "$cmd"
}
# Upload a file to the server
upload_file() {
local ip="$1"
local local_path="$2"
local remote_path="$3"
# shellcheck disable=SC2086
scp $SSH_OPTS "$local_path" "root@$ip:$remote_path"
}
# Start an interactive SSH session
interactive_session() {
local ip="$1"
local cmd="$2"
# shellcheck disable=SC2086
ssh -t $SSH_OPTS "root@$ip" "$cmd"
}
# Destroy a Latitude.sh server
destroy_server() {
local server_id="$1"
log_warn "Destroying server $server_id..."
local response
response=$(latitude_api DELETE "/servers/$server_id")
if echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if d.get('errors') else 1)" 2>/dev/null; then
log_error "Failed to destroy server: $response"
return 1
fi
log_info "Server $server_id destroyed"
}
# List all Latitude.sh servers
list_servers() {
local response
response=$(latitude_api GET "/servers")
python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
servers = data.get('data', [])
if not servers:
print('No servers found')
sys.exit(0)
print(f\"{'HOSTNAME':<25} {'ID':<15} {'STATUS':<12} {'PLAN':<15} {'SITE':<10}\")
print('-' * 77)
for s in servers:
attrs = s.get('attributes', {})
hostname = attrs.get('hostname', 'N/A')
sid = str(s.get('id', 'N/A'))
status = attrs.get('status', 'N/A')
plan = attrs.get('plan', 'N/A')
site = attrs.get('site', 'N/A')
print(f'{hostname:<25} {sid:<15} {status:<12} {plan:<15} {site:<10}')
" <<< "$response"
}
# Install basic tools on the server (cloud-init equivalent for Latitude.sh)
install_base_tools() {
local ip="$1"
log_warn "Installing base tools..."
run_server "$ip" "apt-get update -qq && apt-get install -y -qq curl unzip git zsh > /dev/null 2>&1"
log_warn "Installing Bun..."
run_server "$ip" "curl -fsSL https://bun.sh/install | bash"
run_server "$ip" "printf '%s\n' 'export PATH=\"\${HOME}/.bun/bin:\${PATH}\"' >> /root/.bashrc"
run_server "$ip" "printf '%s\n' 'export PATH=\"\${HOME}/.bun/bin:\${PATH}\"' >> /root/.zshrc"
log_info "Base tools installed"
}

76
latitude/nanoclaw.sh Normal file
View file

@ -0,0 +1,76 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "NanoClaw on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and nanoclaw
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing tsx..."
run_server "${LATITUDE_SERVER_IP}" "source ~/.bashrc && bun install -g tsx"
log_warn "Cloning and building nanoclaw..."
run_server "${LATITUDE_SERVER_IP}" "git clone https://github.com/gavrielc/nanoclaw.git ~/nanoclaw && cd ~/nanoclaw && npm install && npm run build"
log_info "NanoClaw installed"
# 7. 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
# 8. Create nanoclaw .env file
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
track_temp_file "${DOTENV_TEMP}"
chmod 600 "${DOTENV_TEMP}"
cat > "${DOTENV_TEMP}" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF
upload_file "${LATITUDE_SERVER_IP}" "${DOTENV_TEMP}" "/root/nanoclaw/.env"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 9. Start nanoclaw
log_warn "Starting nanoclaw..."
log_warn "You will need to scan a WhatsApp QR code to authenticate."
echo ""
interactive_session "${LATITUDE_SERVER_IP}" "cd ~/nanoclaw && source ~/.zshrc && npm run dev"

69
latitude/openclaw.sh Normal file
View file

@ -0,0 +1,69 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "OpenClaw on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and openclaw
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing openclaw..."
run_server "${LATITUDE_SERVER_IP}" "source ~/.bashrc && bun install -g openclaw"
log_info "OpenClaw installed"
# 7. 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
# 8. Configure openclaw
setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" \
"upload_file ${LATITUDE_SERVER_IP}" \
"run_server ${LATITUDE_SERVER_IP}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 9. Start openclaw gateway in background and launch TUI
log_warn "Starting openclaw..."
run_server "${LATITUDE_SERVER_IP}" "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
sleep 2
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && openclaw tui"

46
latitude/opencode.sh Normal file
View file

@ -0,0 +1,46 @@
#!/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/latitude/lib/common.sh)"
fi
log_info "OpenCode on Latitude.sh"
echo ""
ensure_latitude_token
ensure_ssh_key
SERVER_NAME=$(get_server_name)
create_server "${SERVER_NAME}"
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
verify_server_connectivity "${LATITUDE_SERVER_IP}"
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing OpenCode..."
run_server "${LATITUDE_SERVER_IP}" "$(opencode_install_cmd)"
log_info "OpenCode 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
log_warn "Starting OpenCode..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && opencode"

66
latitude/plandex.sh Normal file
View 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/latitude/lib/common.sh)"
fi
log_info "Plandex on Latitude.sh"
echo ""
# 1. Resolve Latitude.sh API token
ensure_latitude_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 server to become active and get IP
wait_for_server_ready "${LATITUDE_SERVER_ID}" 60
# 5. Wait for SSH connectivity
verify_server_connectivity "${LATITUDE_SERVER_IP}"
# 6. Install base tools and Plandex
install_base_tools "${LATITUDE_SERVER_IP}"
log_warn "Installing Plandex..."
run_server "${LATITUDE_SERVER_IP}" "curl -sL https://plandex.ai/install.sh | bash"
# Verify installation succeeded
if ! run_server "${LATITUDE_SERVER_IP}" "command -v plandex &> /dev/null && plandex version &> /dev/null"; then
log_error "Plandex installation verification failed"
log_error "The 'plandex' command is not available or not working properly on server ${LATITUDE_SERVER_IP}"
exit 1
fi
log_info "Plandex installation verified successfully"
# 7. 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
log_warn "Setting up environment variables..."
inject_env_vars_ssh "${LATITUDE_SERVER_IP}" upload_file run_server \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "Latitude.sh server setup completed successfully!"
log_info "Server: ${SERVER_NAME} (ID: ${LATITUDE_SERVER_ID}, IP: ${LATITUDE_SERVER_IP})"
echo ""
# 8. Start Plandex interactively
log_warn "Starting Plandex..."
sleep 1
clear
interactive_session "${LATITUDE_SERVER_IP}" "source ~/.zshrc && plandex"

View file

@ -479,6 +479,22 @@
"image": "Ubuntu 24.04"
},
"notes": "GPU cloud provider with NVIDIA RTX 3080/3090, A100, and H100 instances. European data centers (Iceland, Norway). Requires GENESIS_API_KEY from https://developers.genesiscloud.com/"
},
"latitude": {
"name": "Latitude.sh",
"description": "Latitude.sh bare metal and VM servers via REST API",
"url": "https://www.latitude.sh/",
"type": "api",
"auth": "LATITUDE_API_KEY",
"provision_method": "POST /servers with SSH keys",
"exec_method": "ssh root@IP",
"interactive_method": "ssh -t root@IP",
"defaults": {
"plan": "vm.tiny",
"site": "DAL2",
"os": "ubuntu_24_04_x64_lts"
},
"notes": "Bare metal and VM cloud provider with global locations. Hourly billing. VMs from $0.07/hr. Requires LATITUDE_API_KEY from https://www.latitude.sh/dashboard"
}
},
"matrix": {
@ -715,6 +731,19 @@
"genesiscloud/cline": "implemented",
"genesiscloud/gptme": "implemented",
"genesiscloud/opencode": "implemented",
"genesiscloud/plandex": "implemented"
"genesiscloud/plandex": "implemented",
"latitude/claude": "implemented",
"latitude/openclaw": "implemented",
"latitude/nanoclaw": "implemented",
"latitude/aider": "implemented",
"latitude/goose": "implemented",
"latitude/codex": "implemented",
"latitude/interpreter": "implemented",
"latitude/gemini": "implemented",
"latitude/amazonq": "implemented",
"latitude/cline": "implemented",
"latitude/gptme": "implemented",
"latitude/opencode": "implemented",
"latitude/plandex": "implemented"
}
}