#!/usr/bin/env bash # Spawnpay installer — one-shot Claude Desktop / Cursor / Cline / Codex / Continue setup. # Usage: # curl -fsSL https://spawnpay.ai/install | bash # Idempotent: re-running merges with existing config instead of overwriting. set -eu C_GREEN=$'\033[32m' C_RED=$'\033[31m' C_YELLOW=$'\033[33m' C_BOLD=$'\033[1m' C_DIM=$'\033[2m' C_RESET=$'\033[0m' say() { printf "%s\n" "$*"; } ok() { printf "%s\xe2\x9c\x93 %s%s\n" "$C_GREEN" "$*" "$C_RESET"; } warn() { printf "%s! %s%s\n" "$C_YELLOW" "$*" "$C_RESET"; } err() { printf "%sx %s%s\n" "$C_RED" "$*" "$C_RESET" 1>&2; } say "${C_BOLD}Spawnpay — one-line install${C_RESET}" say "${C_DIM}Adds spawnpay-mcp + 6 free demo MCPs to your agent config.${C_RESET}" say "" # --- pre-flight --- command -v node >/dev/null 2>&1 || { err "node not found. Install Node 18+ from https://nodejs.org and re-run."; exit 1; } NODE_MAJOR=$(node -p 'process.versions.node.split(".")[0]') if [ "$NODE_MAJOR" -lt 18 ]; then err "Node 18+ required (have $NODE_MAJOR)."; exit 1; fi ok "Node $(node -v) detected." command -v npx >/dev/null 2>&1 || { err "npx not found (comes with npm)."; exit 1; } ok "npx available." if ! command -v jq >/dev/null 2>&1; then warn "jq not installed — falling back to Node-based JSON merge." USE_JQ=0 else USE_JQ=1 fi # --- detect config targets --- OS_NAME=$(uname -s) CLAUDE_CFG="" case "$OS_NAME" in Darwin) CLAUDE_CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json" ;; Linux) CLAUDE_CFG="$HOME/.config/Claude/claude_desktop_config.json" ;; MINGW*|MSYS*|CYGWIN*) CLAUDE_CFG="$APPDATA/Claude/claude_desktop_config.json" ;; esac CURSOR_CFG="$HOME/.cursor/mcp.json" CLINE_CFG_DARWIN="$HOME/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json" CLINE_CFG_LINUX="$HOME/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json" TARGETS=() [ -n "$CLAUDE_CFG" ] && [ -d "$(dirname "$CLAUDE_CFG")" ] && TARGETS+=("claude:$CLAUDE_CFG") [ -d "$HOME/.cursor" ] && TARGETS+=("cursor:$CURSOR_CFG") [ -f "$CLINE_CFG_DARWIN" ] && TARGETS+=("cline:$CLINE_CFG_DARWIN") [ -f "$CLINE_CFG_LINUX" ] && TARGETS+=("cline:$CLINE_CFG_LINUX") if [ "${#TARGETS[@]}" -eq 0 ]; then warn "No agent config detected (Claude Desktop / Cursor / Cline)." warn "Spawnpay-mcp will still be runnable via 'npx -y spawnpay-mcp' but no auto-wire-in happened." warn "See https://spawnpay.ai/quickstart for manual setup." exit 0 fi say "" say "Targets to write:" for t in "${TARGETS[@]}"; do say " - ${t%%:*} → ${t#*:}"; done say "" # Skip prompt if STDIN is a pipe (curl ... | bash); otherwise ask. if [ -t 0 ]; then printf "Proceed? [Y/n] " read -r REPLY || REPLY="Y" case "$REPLY" in [Nn]*) say "Aborted."; exit 0;; esac fi # --- the MCP block to merge in --- SP_BLOCK_FILE=$(mktemp) cat > "$SP_BLOCK_FILE" <<'JSON' { "mcpServers": { "spawnpay": { "command": "npx", "args": ["-y", "spawnpay-mcp"] }, "translate": { "command": "npx", "args": ["-y", "https://github.com/Robocular/spawnpay/releases/download/paid-mcps-v0.2.0/paid-translate-mcp-0.1.0.tgz"] }, "weather": { "command": "npx", "args": ["-y", "https://github.com/Robocular/spawnpay/releases/download/paid-mcps-v0.2.0/paid-weather-mcp-0.1.0.tgz"] }, "currency": { "command": "npx", "args": ["-y", "https://github.com/Robocular/spawnpay/releases/download/paid-mcps-v0.2.0/paid-currency-mcp-0.1.0.tgz"] }, "research": { "command": "npx", "args": ["-y", "https://github.com/Robocular/spawnpay/releases/download/paid-mcps-v0.5.0/paid-research-mcp-0.1.0.tgz"] }, "scrape": { "command": "npx", "args": ["-y", "https://github.com/Robocular/spawnpay/releases/download/paid-mcps-v0.2.0/paid-cloud-scrape-mcp-0.2.0.tgz"] }, "screenshot": { "command": "npx", "args": ["-y", "https://github.com/Robocular/spawnpay/releases/download/paid-mcps-v0.2.0/paid-screenshot-mcp-0.2.0.tgz"] } } } JSON merge_json() { local target="$1" local block="$2" mkdir -p "$(dirname "$target")" if [ ! -f "$target" ] || ! grep -q '{' "$target" 2>/dev/null; then cp "$block" "$target" ok "Wrote fresh config: $target" return fi # Back up. cp "$target" "$target.spawnpay-backup.$(date +%s)" if [ "$USE_JQ" -eq 1 ]; then jq -s '.[0] * .[1]' "$target" "$block" > "$target.tmp" && mv "$target.tmp" "$target" else node -e " const fs = require('fs'); const cur = JSON.parse(fs.readFileSync('$target', 'utf8')); const add = JSON.parse(fs.readFileSync('$block', 'utf8')); cur.mcpServers = Object.assign({}, cur.mcpServers || {}, add.mcpServers); fs.writeFileSync('$target', JSON.stringify(cur, null, 2)); " fi ok "Merged into: $target" } for t in "${TARGETS[@]}"; do case "${t%%:*}" in claude|cursor|cline) merge_json "${t#*:}" "$SP_BLOCK_FILE" ;; esac done rm -f "$SP_BLOCK_FILE" say "" ok "Done." say "" say "${C_BOLD}Next:${C_RESET}" say " 1. Restart your agent (Claude Desktop / Cursor / Cline)." say " 2. Ask it to call a tool. For example:" say " ${C_DIM}\"Use the translate tool to say 'hello' in French.\"${C_RESET}" say " 3. First call auto-creates a Spawnpay wallet with \$7 USDC free credit." say "" say "Learn more: https://spawnpay.ai/quickstart" say "Pricing: https://spawnpay.ai/pricing" say "Top up: https://spawnpay.ai/deposit"