From 007848f8ead2cbe98db598d63eb845c6496860aa Mon Sep 17 00:00:00 2001 From: A <6723574+louisgv@users.noreply.github.com> Date: Fri, 13 Feb 2026 20:30:15 +0000 Subject: [PATCH] feat: Add HOSTKEY support for nanoclaw, aider, goose, cline, continue Implements 5 missing HOSTKEY matrix entries: - hostkey/nanoclaw - hostkey/aider - hostkey/goose - hostkey/cline - hostkey/continue All scripts follow the standard pattern: 1. Authenticate with HOSTKEY 2. Create server instance 3. Install agent 4. Configure OpenRouter API key injection 5. Launch interactive session Agent: gap-filler Co-Authored-By: Claude Sonnet 4.5 --- hostkey/aider.sh | 77 +++++++++++++++++++++++++++++++++++++++++++++ hostkey/cline.sh | 59 ++++++++++++++++++++++++++++++++++ hostkey/continue.sh | 60 +++++++++++++++++++++++++++++++++++ hostkey/goose.sh | 63 +++++++++++++++++++++++++++++++++++++ hostkey/nanoclaw.sh | 76 ++++++++++++++++++++++++++++++++++++++++++++ manifest.json | 28 ++++++++--------- 6 files changed, 349 insertions(+), 14 deletions(-) create mode 100644 hostkey/aider.sh create mode 100644 hostkey/cline.sh create mode 100644 hostkey/continue.sh create mode 100644 hostkey/goose.sh create mode 100644 hostkey/nanoclaw.sh diff --git a/hostkey/aider.sh b/hostkey/aider.sh new file mode 100644 index 000000000..10fa07499 --- /dev/null +++ b/hostkey/aider.sh @@ -0,0 +1,77 @@ +#!/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)" +# shellcheck source=hostkey/lib/common.sh +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/hostkey/lib/common.sh)" +fi + +log_info "Aider on HOSTKEY" +echo "" + +# 1. Resolve HOSTKEY API token +ensure_hostkey_token + +# 2. Generate + register SSH key +ensure_ssh_key + +# 3. Get server name and create server +SERVER_NAME=$(get_server_name) +create_server "${SERVER_NAME}" + +# 4. Wait for SSH and cloud-init +verify_server_connectivity "${HOSTKEY_INSTANCE_IP}" +wait_for_cloud_init "${HOSTKEY_INSTANCE_IP}" 60 + +# 5. Install Aider +log_step "Installing Aider..." +run_server "${HOSTKEY_INSTANCE_IP}" "pip install aider-chat 2>/dev/null || pip3 install aider-chat" + +# Verify installation succeeded +if ! run_server "${HOSTKEY_INSTANCE_IP}" "command -v aider &> /dev/null && aider --version &> /dev/null"; then + log_install_failed "Aider" "pip install aider-chat" "${HOSTKEY_INSTANCE_IP}" + exit 1 +fi +log_info "Aider installation verified successfully" + +# 6. Get OpenRouter API key via OAuth +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +# Get model preference +MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1 + +log_step "Setting up environment variables..." +inject_env_vars_ssh "${HOSTKEY_INSTANCE_IP}" upload_file run_server \ + "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" + +echo "" +log_info "HOSTKEY server setup completed successfully!" +log_info "Server: ${SERVER_NAME} (ID: ${HOSTKEY_INSTANCE_ID}, IP: ${HOSTKEY_INSTANCE_IP})" +echo "" + +# Check if running in non-interactive mode +if [[ -n "${SPAWN_PROMPT:-}" ]]; then + # Non-interactive mode: execute prompt and exit + log_step "Executing Aider with prompt..." + + # Escape prompt for safe shell execution + escaped_prompt=$(printf '%q' "${SPAWN_PROMPT}") + + # Execute without interactive session, using -m (message) for non-interactive execution + run_server "${HOSTKEY_INSTANCE_IP}" "source ~/.zshrc && aider --model openrouter/${MODEL_ID} -m ${escaped_prompt}" +else + # Interactive mode: start Aider normally + log_step "Starting Aider..." + sleep 1 + clear + interactive_session "${HOSTKEY_INSTANCE_IP}" "source ~/.zshrc && aider --model openrouter/${MODEL_ID}" +fi diff --git a/hostkey/cline.sh b/hostkey/cline.sh new file mode 100644 index 000000000..a70afe38b --- /dev/null +++ b/hostkey/cline.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# shellcheck disable=SC2154 +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)" +# shellcheck source=hostkey/lib/common.sh +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/hostkey/lib/common.sh)" +fi + +log_info "Cline on HOSTKEY" +echo "" + +# 1. Resolve HOSTKEY API token +ensure_hostkey_token + +# 2. Generate + register SSH key +ensure_ssh_key + +# 3. Get server name and create server +SERVER_NAME=$(get_server_name) +create_server "${SERVER_NAME}" + +# 4. Wait for SSH and cloud-init +verify_server_connectivity "${HOSTKEY_INSTANCE_IP}" +wait_for_cloud_init "${HOSTKEY_INSTANCE_IP}" 60 + +# 5. Install Cline +log_step "Installing Cline..." +run_server "${HOSTKEY_INSTANCE_IP}" "npm install -g cline" +log_info "Cline installed" + +# 6. Get OpenRouter API key +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +log_step "Setting up environment variables..." +inject_env_vars_ssh "${HOSTKEY_INSTANCE_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 "HOSTKEY server setup completed successfully!" +log_info "Server: ${SERVER_NAME} (ID: ${HOSTKEY_INSTANCE_ID}, IP: ${HOSTKEY_INSTANCE_IP})" +echo "" + +# 7. Start Cline +log_step "Starting Cline..." +sleep 1 +clear +interactive_session "${HOSTKEY_INSTANCE_IP}" "source ~/.zshrc && cline" diff --git a/hostkey/continue.sh b/hostkey/continue.sh new file mode 100644 index 000000000..a00df99ec --- /dev/null +++ b/hostkey/continue.sh @@ -0,0 +1,60 @@ +#!/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)" +# shellcheck source=hostkey/lib/common.sh +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/hostkey/lib/common.sh)" +fi + +log_info "Continue on HOSTKEY" +echo "" + +# 1. Resolve HOSTKEY API token +ensure_hostkey_token + +# 2. Generate + register SSH key +ensure_ssh_key + +# 3. Get server name and create server +SERVER_NAME=$(get_server_name) +create_server "${SERVER_NAME}" + +# 4. Wait for SSH and cloud-init +verify_server_connectivity "${HOSTKEY_INSTANCE_IP}" +wait_for_cloud_init "${HOSTKEY_INSTANCE_IP}" 60 + +# 5. Install Continue CLI +log_step "Installing Continue CLI..." +run_server "${HOSTKEY_INSTANCE_IP}" "npm install -g @continuedev/cli" + +# 6. Get OpenRouter API key +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +log_step "Setting up environment variables..." +inject_env_vars_ssh "${HOSTKEY_INSTANCE_IP}" upload_file run_server \ + "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" + +# 7. Configure Continue +setup_continue_config "${OPENROUTER_API_KEY}" \ + "upload_file ${HOSTKEY_INSTANCE_IP}" \ + "run_server ${HOSTKEY_INSTANCE_IP}" + +echo "" +log_info "HOSTKEY server setup completed successfully!" +log_info "Server: ${SERVER_NAME} (ID: ${HOSTKEY_INSTANCE_ID}, IP: ${HOSTKEY_INSTANCE_IP})" +echo "" + +# 8. Start Continue CLI in TUI mode +log_step "Starting Continue CLI in TUI mode..." +sleep 1 +clear +interactive_session "${HOSTKEY_INSTANCE_IP}" "source ~/.zshrc && cn" diff --git a/hostkey/goose.sh b/hostkey/goose.sh new file mode 100644 index 000000000..95bb3ca19 --- /dev/null +++ b/hostkey/goose.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -eo pipefail + +# Source common functions - try local file first, fall back to remote +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" +# shellcheck source=hostkey/lib/common.sh +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/hostkey/lib/common.sh)" +fi + +log_info "Goose on HOSTKEY" +echo "" + +# 1. Resolve HOSTKEY API token +ensure_hostkey_token + +# 2. Generate + register SSH key +ensure_ssh_key + +# 3. Get server name and create server +SERVER_NAME=$(get_server_name) +create_server "${SERVER_NAME}" + +# 4. Wait for SSH and cloud-init +verify_server_connectivity "${HOSTKEY_INSTANCE_IP}" +wait_for_cloud_init "${HOSTKEY_INSTANCE_IP}" 60 + +# 5. Install Goose +log_step "Installing Goose..." +run_server "${HOSTKEY_INSTANCE_IP}" "CONFIGURE=false curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash" + +# Verify installation succeeded +if ! run_server "${HOSTKEY_INSTANCE_IP}" "command -v goose &> /dev/null && goose --version &> /dev/null"; then + log_install_failed "Goose" "CONFIGURE=false curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash" "${HOSTKEY_INSTANCE_IP}" + exit 1 +fi +log_info "Goose installation verified successfully" + +# 6. Get OpenRouter API key +echo "" +if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + log_info "Using OpenRouter API key from environment" +else + OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180) +fi + +log_step "Setting up environment variables..." +inject_env_vars_ssh "${HOSTKEY_INSTANCE_IP}" upload_file run_server \ + "GOOSE_PROVIDER=openrouter" \ + "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" + +echo "" +log_info "HOSTKEY server setup completed successfully!" +log_info "Server: ${SERVER_NAME} (ID: ${HOSTKEY_INSTANCE_ID}, IP: ${HOSTKEY_INSTANCE_IP})" +echo "" + +# 7. Start Goose interactively +log_step "Starting Goose..." +sleep 1 +clear +interactive_session "${HOSTKEY_INSTANCE_IP}" "source ~/.zshrc && goose" diff --git a/hostkey/nanoclaw.sh b/hostkey/nanoclaw.sh new file mode 100644 index 000000000..9c0e756ac --- /dev/null +++ b/hostkey/nanoclaw.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)" +# shellcheck source=hostkey/lib/common.sh +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/hostkey/lib/common.sh)" +fi + +log_info "NanoClaw on HOSTKEY" +echo "" + +# 1. Resolve HOSTKEY API token +ensure_hostkey_token + +# 2. Generate + register SSH key +ensure_ssh_key + +# 3. Get server name and create server +SERVER_NAME=$(get_server_name) +create_server "${SERVER_NAME}" + +# 4. Wait for SSH and cloud-init +verify_server_connectivity "${HOSTKEY_INSTANCE_IP}" +wait_for_cloud_init "${HOSTKEY_INSTANCE_IP}" 60 + +# 5. Install Node.js dependencies +log_step "Installing Node.js dependencies..." +run_server "${HOSTKEY_INSTANCE_IP}" "npm install -g tsx" + +# 6. Clone nanoclaw +log_step "Cloning nanoclaw..." +run_server "${HOSTKEY_INSTANCE_IP}" "git clone https://github.com/gavrielc/nanoclaw.git ~/nanoclaw && cd ~/nanoclaw && npm install && npm run build" + +# 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_step "Setting up environment variables..." +inject_env_vars_ssh "${HOSTKEY_INSTANCE_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_step "Configuring nanoclaw..." + +DOTENV_TEMP=$(mktemp) +trap 'rm -f "${DOTENV_TEMP}"' EXIT +chmod 600 "${DOTENV_TEMP}" +cat > "${DOTENV_TEMP}" << EOF +ANTHROPIC_API_KEY=${OPENROUTER_API_KEY} +EOF + +upload_file "${HOSTKEY_INSTANCE_IP}" "${DOTENV_TEMP}" "/tmp/nanoclaw_env" +run_server "${HOSTKEY_INSTANCE_IP}" "mv /tmp/nanoclaw_env ~/nanoclaw/.env" + +echo "" +log_info "HOSTKEY server setup completed successfully!" +log_info "Server: ${SERVER_NAME} (ID: ${HOSTKEY_INSTANCE_ID}, IP: ${HOSTKEY_INSTANCE_IP})" +echo "" + +# 9. Start nanoclaw +log_step "Starting nanoclaw..." +log_info "You will need to scan a WhatsApp QR code to authenticate." +echo "" +sleep 1 +clear +interactive_session "${HOSTKEY_INSTANCE_IP}" "cd ~/nanoclaw && source ~/.zshrc && npm run dev" diff --git a/manifest.json b/manifest.json index 94dafa689..a6c2f6d6b 100644 --- a/manifest.json +++ b/manifest.json @@ -372,7 +372,7 @@ "defaults": { "template": "base" }, - "notes": "No SSH — uses CodeSandbox SDK/CLI for exec. Firecracker microVMs with ~2s start. Free tier: 40 hrs/mo on Build plan. Requires npm install -g @codesandbox/sdk." + "notes": "No SSH \u2014 uses CodeSandbox SDK/CLI for exec. Firecracker microVMs with ~2s start. Free tier: 40 hrs/mo on Build plan. Requires npm install -g @codesandbox/sdk." }, "e2b": { "name": "E2B", @@ -386,7 +386,7 @@ "defaults": { "template": "base" }, - "notes": "No SSH — uses E2B CLI for exec. Sandboxes start in ~150ms. Requires npm install -g @e2b/cli." + "notes": "No SSH \u2014 uses E2B CLI for exec. Sandboxes start in ~150ms. Requires npm install -g @e2b/cli." }, "modal": { "name": "Modal", @@ -400,7 +400,7 @@ "defaults": { "image": "debian_slim" }, - "notes": "No SSH — uses Modal Python SDK for exec. Sub-second cold starts. Requires pip install modal." + "notes": "No SSH \u2014 uses Modal Python SDK for exec. Sub-second cold starts. Requires pip install modal." }, "fly": { "name": "Fly.io", @@ -656,7 +656,7 @@ "disk_size": 20, "location": "us/las" }, - "notes": "Budget European cloud provider with minute-based billing starting at $2/month. Requires IONOS_USERNAME (email) and IONOS_PASSWORD (API key) from https://dcd.ionos.com/ → Management → Users & Keys. Datacenters are created automatically if needed." + "notes": "Budget European cloud provider with minute-based billing starting at $2/month. Requires IONOS_USERNAME (email) and IONOS_PASSWORD (API key) from https://dcd.ionos.com/ \u2192 Management \u2192 Users & Keys. Datacenters are created automatically if needed." }, "exoscale": { "name": "Exoscale", @@ -704,7 +704,7 @@ "location": "eu-central", "os_template": "ubuntu-24.04" }, - "notes": "Budget VPS provider with cloud-init pre-installed. Starting at $4.95/mo. Requires HOSTINGER_API_KEY from hPanel → Profile → Account Information → API. API docs at developers.hostinger.com" + "notes": "Budget VPS provider with cloud-init pre-installed. Starting at $4.95/mo. Requires HOSTINGER_API_KEY from hPanel \u2192 Profile \u2192 Account Information \u2192 API. API docs at developers.hostinger.com" }, "netcup": { "name": "Netcup", @@ -720,7 +720,7 @@ "datacenter": "Nuremberg", "image": "ubuntu-24.04" }, - "notes": "German budget VPS provider starting at €3.86/mo. Session-based REST API launched Oct 2025. Requires NETCUP_CUSTOMER_NUMBER, NETCUP_API_KEY, and NETCUP_API_PASSWORD from https://ccp.netcup.net/ → Settings → API" + "notes": "German budget VPS provider starting at \u20ac3.86/mo. Session-based REST API launched Oct 2025. Requires NETCUP_CUSTOMER_NUMBER, NETCUP_API_KEY, and NETCUP_API_PASSWORD from https://ccp.netcup.net/ \u2192 Settings \u2192 API" }, "local": { "name": "Local Machine", @@ -746,7 +746,7 @@ "flavor": "1GB", "image": "Ubuntu 24.04" }, - "notes": "Budget VPS provider with OpenStack API. Hourly billing starting at $0.006/hr (~$4.38/mo). Full OpenStack compatibility. Get credentials from https://manage.ramnode.com/ → Cloud → API Users. Requires RAMNODE_USERNAME, RAMNODE_PASSWORD, and RAMNODE_PROJECT_ID." + "notes": "Budget VPS provider with OpenStack API. Hourly billing starting at $0.006/hr (~$4.38/mo). Full OpenStack compatibility. Get credentials from https://manage.ramnode.com/ \u2192 Cloud \u2192 API Users. Requires RAMNODE_USERNAME, RAMNODE_PASSWORD, and RAMNODE_PROJECT_ID." }, "atlanticnet": { "name": "Atlantic.Net", @@ -762,7 +762,7 @@ "location": "USEAST2", "image": "ubuntu-24.04_64bit" }, - "notes": "Budget VPS provider starting at $4/mo. Uses HMAC-SHA256 signature auth with API key + private key. Requires ATLANTICNET_API_KEY and ATLANTICNET_API_PRIVATE_KEY from https://cloud.atlantic.net/ → API Info." + "notes": "Budget VPS provider starting at $4/mo. Uses HMAC-SHA256 signature auth with API key + private key. Requires ATLANTICNET_API_KEY and ATLANTICNET_API_PRIVATE_KEY from https://cloud.atlantic.net/ \u2192 API Info." }, "hostkey": { "name": "HOSTKEY", @@ -1310,19 +1310,19 @@ "atlanticnet/continue": "implemented", "hostkey/claude": "implemented", "hostkey/openclaw": "implemented", - "hostkey/nanoclaw": "missing", - "hostkey/aider": "missing", - "hostkey/goose": "missing", + "hostkey/nanoclaw": "implemented", + "hostkey/aider": "implemented", + "hostkey/goose": "implemented", "hostkey/codex-cli": "missing", "hostkey/open-interpreter": "missing", "hostkey/gemini-cli": "missing", "hostkey/amazonq": "missing", - "hostkey/cline": "missing", + "hostkey/cline": "implemented", "hostkey/gptme": "missing", "hostkey/opencode": "missing", "hostkey/plandex": "missing", "hostkey/kilo": "missing", - "hostkey/continue": "missing", + "hostkey/continue": "implemented", "cloudsigma/claude": "implemented", "cloudsigma/openclaw": "missing", "cloudsigma/nanoclaw": "missing", @@ -1339,4 +1339,4 @@ "cloudsigma/kilocode": "missing", "cloudsigma/continue": "missing" } -} +} \ No newline at end of file