mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-19 16:39:50 +00:00
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:
parent
95a629ef36
commit
89ed02fe55
17 changed files with 1363 additions and 17 deletions
32
README.md
32
README.md
|
|
@ -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
119
latitude/README.md
Normal 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
69
latitude/aider.sh
Normal 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
48
latitude/amazonq.sh
Normal 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
76
latitude/claude.sh
Normal 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
48
latitude/cline.sh
Normal 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
48
latitude/codex.sh
Normal 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
49
latitude/gemini.sh
Normal 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
67
latitude/goose.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/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
69
latitude/gptme.sh
Normal 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
48
latitude/interpreter.sh
Normal 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
419
latitude/lib/common.sh
Normal 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
76
latitude/nanoclaw.sh
Normal 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
69
latitude/openclaw.sh
Normal 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
46
latitude/opencode.sh
Normal 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
66
latitude/plandex.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/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"
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue