diff --git a/github-codespaces/README.md b/github-codespaces/README.md new file mode 100644 index 00000000..c994bf57 --- /dev/null +++ b/github-codespaces/README.md @@ -0,0 +1,67 @@ +# GitHub Codespaces + +GitHub Codespaces development environments via gh CLI. [GitHub Codespaces](https://github.com/features/codespaces) + +## Agents + +#### Claude Code + +```bash +bash <(curl -fsSL https://openrouter.ai/lab/spawn/github-codespaces/claude.sh) +``` + +#### Aider + +```bash +bash <(curl -fsSL https://openrouter.ai/lab/spawn/github-codespaces/aider.sh) +``` + +#### gptme + +```bash +bash <(curl -fsSL https://openrouter.ai/lab/spawn/github-codespaces/gptme.sh) +``` + +## Non-Interactive Mode + +```bash +GITHUB_REPO=OpenRouterTeam/spawn \ +OPENROUTER_API_KEY=sk-or-v1-xxxxx \ + bash <(curl -fsSL https://openrouter.ai/lab/spawn/github-codespaces/claude.sh) +``` + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `GITHUB_REPO` | Repository for codespace | `OpenRouterTeam/spawn` | +| `CODESPACE_MACHINE` | Machine type | `basicLinux32gb` | +| `CODESPACE_IDLE_TIMEOUT` | Idle timeout | `30m` | +| `OPENROUTER_API_KEY` | OpenRouter API key | _(OAuth or prompted)_ | + +## Pricing + +GitHub Codespaces uses pay-as-you-go pricing: + +- **Compute**: Starting at $0.18/hr for basicLinux32gb (2 core, 4GB RAM) +- **Storage**: $0.07/GB per month +- **Free tier**: Available for personal accounts (limited hours/month) + +See [GitHub Codespaces pricing](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces) for details. + +## Prerequisites + +- GitHub CLI (`gh`) installed and authenticated +- Active GitHub account +- Repository access (default: OpenRouterTeam/spawn) + +## Machine Types + +| Machine | Cores | RAM | Price/hr | +|---------|-------|-----|----------| +| basicLinux32gb | 2 | 4GB | $0.18 | +| standardLinux32gb | 4 | 8GB | $0.36 | +| premiumLinux | 8 | 16GB | $0.72 | +| largePremiumLinux | 16 | 32GB | $1.44 | + +Set via `CODESPACE_MACHINE` environment variable. diff --git a/github-codespaces/aider.sh b/github-codespaces/aider.sh new file mode 100755 index 00000000..125b6e7e --- /dev/null +++ b/github-codespaces/aider.sh @@ -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/github-codespaces/lib/common.sh)" +fi + +log_info "Aider on GitHub Codespaces" +echo "" + +# 1. Ensure gh CLI and authentication +ensure_gh_cli +ensure_gh_auth + +# 2. Get repository and create codespace +REPO="${GITHUB_REPO:-OpenRouterTeam/spawn}" +MACHINE="${CODESPACE_MACHINE:-basicLinux32gb}" +IDLE_TIMEOUT="${CODESPACE_IDLE_TIMEOUT:-30m}" + +log_info "Creating codespace for repo: $REPO" +CODESPACE=$(create_codespace "$REPO" "$MACHINE" "$IDLE_TIMEOUT") + +if [[ -z "$CODESPACE" ]]; then + log_error "Failed to create codespace" + exit 1 +fi + +log_info "Codespace created: $CODESPACE" + +# 3. Wait for codespace to be ready +wait_for_codespace "$CODESPACE" + +# 4. Install Aider +log_warn "Installing Aider..." +run_in_codespace "$CODESPACE" "pip install aider-chat 2>/dev/null || pip3 install aider-chat" +log_info "Aider installed" + +# 5. Get OpenRouter API key +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +# 6. Get model preference +MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1 + +# 7. Inject environment variables into ~/.bashrc +log_warn "Setting up environment variables..." + +ENV_VARS=" +export OPENROUTER_API_KEY='${OPENROUTER_API_KEY}' +" + +run_in_codespace "$CODESPACE" "cat >> ~/.bashrc << 'ENVEOF' +$ENV_VARS +ENVEOF" + +echo "" +log_info "GitHub Codespace setup completed successfully!" +log_info "Codespace: $CODESPACE" +echo "" + +# 8. Start Aider interactively +log_warn "Starting Aider..." +log_warn "To delete codespace later, run: gh codespace delete --codespace $CODESPACE --force" +echo "" +sleep 1 + +# Launch Aider with model +run_in_codespace "$CODESPACE" "source ~/.bashrc && aider --model openrouter/${MODEL_ID}" diff --git a/github-codespaces/claude.sh b/github-codespaces/claude.sh new file mode 100755 index 00000000..d6b3406d --- /dev/null +++ b/github-codespaces/claude.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -eo pipefail + +# Source common functions - try local file first, fall back to remote +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" +if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then + source "$SCRIPT_DIR/lib/common.sh" +else + eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/github-codespaces/lib/common.sh)" +fi + +log_info "Claude Code on GitHub Codespaces" +echo "" + +# 1. Ensure gh CLI and authentication +ensure_gh_cli +ensure_gh_auth + +# 2. Get repository and create codespace +REPO="${GITHUB_REPO:-OpenRouterTeam/spawn}" +MACHINE="${CODESPACE_MACHINE:-basicLinux32gb}" +IDLE_TIMEOUT="${CODESPACE_IDLE_TIMEOUT:-30m}" + +log_info "Creating codespace for repo: $REPO" +CODESPACE=$(create_codespace "$REPO" "$MACHINE" "$IDLE_TIMEOUT") + +if [[ -z "$CODESPACE" ]]; then + log_error "Failed to create codespace" + exit 1 +fi + +log_info "Codespace created: $CODESPACE" + +# 3. Wait for codespace to be ready +wait_for_codespace "$CODESPACE" + +# 4. Install Claude Code +log_warn "Installing Claude Code..." +run_in_codespace "$CODESPACE" "curl -fsSL https://claude.ai/install.sh | bash" + +# Verify installation +if ! run_in_codespace "$CODESPACE" "command -v claude" &>/dev/null; then + log_error "Claude Code installation failed" + delete_codespace "$CODESPACE" + exit 1 +fi +log_info "Claude Code installed" + +# 5. Get OpenRouter API key +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +# 6. Inject environment variables into ~/.bashrc +log_warn "Setting up environment variables..." + +ENV_VARS=" +export OPENROUTER_API_KEY='${OPENROUTER_API_KEY}' +export ANTHROPIC_BASE_URL='https://openrouter.ai/api' +export ANTHROPIC_AUTH_TOKEN='${OPENROUTER_API_KEY}' +export ANTHROPIC_API_KEY='' +export CLAUDE_CODE_SKIP_ONBOARDING='1' +export CLAUDE_CODE_ENABLE_TELEMETRY='0' +export PATH=\"\$HOME/.claude/local/bin:\$HOME/.bun/bin:\$PATH\" +" + +run_in_codespace "$CODESPACE" "cat >> ~/.bashrc << 'ENVEOF' +$ENV_VARS +ENVEOF" + +# 7. Configure Claude Code settings +log_warn "Configuring Claude Code..." + +run_in_codespace "$CODESPACE" "mkdir -p ~/.claude" + +# Create settings.json +SETTINGS_JSON="{ + \"theme\": \"dark\", + \"editor\": \"vim\", + \"env\": { + \"CLAUDE_CODE_ENABLE_TELEMETRY\": \"0\", + \"ANTHROPIC_BASE_URL\": \"https://openrouter.ai/api\", + \"ANTHROPIC_AUTH_TOKEN\": \"${OPENROUTER_API_KEY}\" + }, + \"permissions\": { + \"defaultMode\": \"bypassPermissions\", + \"dangerouslySkipPermissions\": true + } +}" + +run_in_codespace "$CODESPACE" "cat > ~/.claude/settings.json << 'SETTINGSEOF' +$SETTINGS_JSON +SETTINGSEOF" + +# Create global state file +GLOBAL_STATE="{ + \"hasCompletedOnboarding\": true, + \"bypassPermissionsModeAccepted\": true +}" + +run_in_codespace "$CODESPACE" "cat > ~/.claude.json << 'STATEEOF' +$GLOBAL_STATE +STATEEOF" + +# Create empty CLAUDE.md +run_in_codespace "$CODESPACE" "touch ~/.claude/CLAUDE.md" + +echo "" +log_info "Setup complete. Opening interactive session..." +echo "" +log_warn "To delete codespace later, run: gh codespace delete --codespace $CODESPACE --force" +echo "" + +# 8. Source env vars and launch Claude +ssh_to_codespace "$CODESPACE" diff --git a/github-codespaces/gptme.sh b/github-codespaces/gptme.sh new file mode 100755 index 00000000..d567ab79 --- /dev/null +++ b/github-codespaces/gptme.sh @@ -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/github-codespaces/lib/common.sh)" +fi + +log_info "gptme on GitHub Codespaces" +echo "" + +# 1. Ensure gh CLI and authentication +ensure_gh_cli +ensure_gh_auth + +# 2. Get repository and create codespace +REPO="${GITHUB_REPO:-OpenRouterTeam/spawn}" +MACHINE="${CODESPACE_MACHINE:-basicLinux32gb}" +IDLE_TIMEOUT="${CODESPACE_IDLE_TIMEOUT:-30m}" + +log_info "Creating codespace for repo: $REPO" +CODESPACE=$(create_codespace "$REPO" "$MACHINE" "$IDLE_TIMEOUT") + +if [[ -z "$CODESPACE" ]]; then + log_error "Failed to create codespace" + exit 1 +fi + +log_info "Codespace created: $CODESPACE" + +# 3. Wait for codespace to be ready +wait_for_codespace "$CODESPACE" + +# 4. Install gptme +log_warn "Installing gptme..." +run_in_codespace "$CODESPACE" "pip install gptme 2>/dev/null || pip3 install gptme" +log_info "gptme installed" + +# 5. Get OpenRouter API key +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +# 6. Get model preference +MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1 + +# 7. Inject environment variables into ~/.bashrc +log_warn "Setting up environment variables..." + +ENV_VARS=" +export OPENROUTER_API_KEY='${OPENROUTER_API_KEY}' +" + +run_in_codespace "$CODESPACE" "cat >> ~/.bashrc << 'ENVEOF' +$ENV_VARS +ENVEOF" + +echo "" +log_info "GitHub Codespace setup completed successfully!" +log_info "Codespace: $CODESPACE" +echo "" + +# 8. Start gptme interactively +log_warn "Starting gptme..." +log_warn "To delete codespace later, run: gh codespace delete --codespace $CODESPACE --force" +echo "" +sleep 1 + +# Launch gptme with model +run_in_codespace "$CODESPACE" "source ~/.bashrc && gptme -m openrouter/${MODEL_ID}" diff --git a/github-codespaces/lib/common.sh b/github-codespaces/lib/common.sh new file mode 100755 index 00000000..2dda46b1 --- /dev/null +++ b/github-codespaces/lib/common.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# Common bash functions for GitHub Codespaces spawn scripts +# Uses GitHub CLI (gh) for provisioning and SSH access + +# Bash safety flags +set -eo pipefail + +# ============================================================ +# Provider-agnostic functions +# ============================================================ + +# Source shared provider-agnostic functions (local or remote fallback) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" +if [[ -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/../../shared/common.sh" ]]; then + source "$SCRIPT_DIR/../../shared/common.sh" +else + eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/shared/common.sh)" +fi + +# Note: Provider-agnostic functions (logging, OAuth, browser, nc_listen) are now in shared/common.sh + +# ============================================================ +# GitHub Codespaces specific functions +# ============================================================ + +# Ensure gh CLI is installed +ensure_gh_cli() { + if command -v gh &>/dev/null; then + log_info "GitHub CLI (gh) available" + return 0 + fi + + log_warn "Installing GitHub CLI (gh)..." + + # Detect OS and install accordingly + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v brew &>/dev/null; then + brew install gh || { + log_error "Failed to install gh via Homebrew" + return 1 + } + else + log_error "Homebrew not found. Install from https://cli.github.com/" + return 1 + fi + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt update + sudo apt install gh -y || { + log_error "Failed to install gh via apt" + return 1 + } + else + log_error "Unsupported OS. Install from https://cli.github.com/" + return 1 + fi + + if ! command -v gh &>/dev/null; then + log_error "gh not found in PATH after installation" + return 1 + fi + + log_info "GitHub CLI (gh) installed" +} + +# Ensure user is authenticated with gh CLI +ensure_gh_auth() { + if ! gh auth status &>/dev/null; then + log_warn "Not authenticated with GitHub CLI" + log_info "Initiating GitHub CLI authentication..." + gh auth login || { + log_error "Failed to authenticate with GitHub CLI" + log_error "Run: gh auth login" + return 1 + } + fi + log_info "Authenticated with GitHub CLI" + return 0 +} + +# Create a new codespace +# Args: $1 = repo (e.g., "OpenRouterTeam/spawn") +# $2 = machine type (optional, default: basicLinux32gb) +# $3 = idle timeout (optional, default: 30m) +create_codespace() { + local repo="$1" + local machine="${2:-basicLinux32gb}" + local idle_timeout="${3:-30m}" + + log_info "Creating GitHub Codespace..." + log_info "Repo: $repo" + log_info "Machine: $machine" + log_info "Idle timeout: $idle_timeout" + + local codespace_name + codespace_name=$(gh codespace create \ + --repo "$repo" \ + --machine "$machine" \ + --idle-timeout "$idle_timeout" \ + 2>&1) + + if [[ $? -ne 0 ]]; then + log_error "Failed to create codespace" + log_error "$codespace_name" + return 1 + fi + + echo "$codespace_name" +} + +# Wait for codespace to be ready +# Args: $1 = codespace name +wait_for_codespace() { + local codespace="$1" + local max_attempts=60 + local attempt=0 + + log_info "Waiting for codespace to be ready..." + + while [[ $attempt -lt $max_attempts ]]; do + local state + state=$(gh codespace view --codespace "$codespace" --json state --jq '.state' 2>/dev/null || echo "Unknown") + + if [[ "$state" == "Available" ]]; then + log_info "Codespace is ready" + return 0 + fi + + attempt=$((attempt + 1)) + sleep 2 + done + + log_error "Codespace failed to become ready after $max_attempts attempts" + return 1 +} + +# Run command in codespace +# Args: $1 = codespace name +# $2+ = command to run +run_in_codespace() { + local codespace="$1" + shift + gh codespace ssh --codespace "$codespace" -- "$@" +} + +# Copy file to codespace +# Args: $1 = codespace name +# $2 = source file +# $3 = destination path +copy_to_codespace() { + local codespace="$1" + local source="$2" + local dest="$3" + gh codespace cp "$source" "$codespace:$dest" +} + +# Open interactive SSH session in codespace +# Args: $1 = codespace name +ssh_to_codespace() { + local codespace="$1" + log_info "Opening SSH session to codespace..." + gh codespace ssh --codespace "$codespace" +} + +# Delete a codespace +# Args: $1 = codespace name +delete_codespace() { + local codespace="$1" + log_info "Deleting codespace $codespace..." + gh codespace delete --codespace "$codespace" --force || { + log_warn "Failed to delete codespace (may already be deleted)" + } +} + +# Get codespace info +# Args: $1 = codespace name +get_codespace_info() { + local codespace="$1" + gh codespace view --codespace "$codespace" --json name,state,machine,repository,idleTimeoutNotice +} diff --git a/manifest.json b/manifest.json index 518821bd..20b1390c 100644 --- a/manifest.json +++ b/manifest.json @@ -335,6 +335,22 @@ }, "notes": "Uses current username for SSH. Requires gcloud CLI installed and configured." }, + "github-codespaces": { + "name": "GitHub Codespaces", + "description": "GitHub Codespaces development environments via gh CLI", + "url": "https://github.com/features/codespaces", + "type": "cli", + "auth": "gh auth login", + "provision_method": "gh codespace create", + "exec_method": "gh codespace ssh", + "interactive_method": "gh codespace ssh (PTY)", + "defaults": { + "machine": "basicLinux32gb", + "repo": "OpenRouterTeam/spawn", + "idle_timeout": "30m" + }, + "notes": "Pay-as-you-go starting at $0.18/hr (2 core, 4GB). Requires gh CLI (GitHub CLI) installed and authenticated. Free tier available for personal accounts." + }, "e2b": { "name": "E2B", "description": "E2B sandboxed cloud containers via CLI/SDK", @@ -732,6 +748,16 @@ "gcp/gemini": "implemented", "gcp/amazonq": "implemented", "gcp/cline": "implemented", + "github-codespaces/claude": "implemented", + "github-codespaces/openclaw": "missing", + "github-codespaces/nanoclaw": "missing", + "github-codespaces/aider": "implemented", + "github-codespaces/goose": "missing", + "github-codespaces/codex": "missing", + "github-codespaces/interpreter": "missing", + "github-codespaces/gemini": "missing", + "github-codespaces/amazonq": "missing", + "github-codespaces/cline": "missing", "e2b/claude": "implemented", "e2b/openclaw": "implemented", "e2b/nanoclaw": "implemented", @@ -760,6 +786,7 @@ "lambda/gptme": "implemented", "aws-lightsail/gptme": "implemented", "gcp/gptme": "implemented", + "github-codespaces/gptme": "implemented", "e2b/gptme": "implemented", "modal/gptme": "implemented", "fly/claude": "implemented", @@ -792,6 +819,7 @@ "lambda/opencode": "implemented", "aws-lightsail/opencode": "implemented", "gcp/opencode": "implemented", + "github-codespaces/opencode": "missing", "e2b/opencode": "implemented", "modal/opencode": "implemented", "fly/opencode": "implemented", @@ -828,6 +856,7 @@ "lambda/plandex": "implemented", "aws-lightsail/plandex": "implemented", "gcp/plandex": "implemented", + "github-codespaces/plandex": "missing", "e2b/plandex": "implemented", "modal/plandex": "implemented", "fly/plandex": "implemented", @@ -933,6 +962,7 @@ "lambda/kilocode": "implemented", "aws-lightsail/kilocode": "implemented", "gcp/kilocode": "implemented", + "github-codespaces/kilocode": "missing", "e2b/kilocode": "implemented", "modal/kilocode": "implemented", "fly/kilocode": "implemented",