#!/bin/sh # crm-cli installer. Downloads the latest CLI bundle, installs to # ~/.crm-cli (or $CRM_CLI_HOME), and symlinks `crm` into PATH. # # Usage: # curl -fsSL https:///install.sh | sh # # Env overrides: # CRM_CLI_BASE_URL Where to fetch crm-cli.tar.gz from. Defaults to the # host this script was served from (baked at deploy). # CRM_CLI_HOME Install dir (default: $HOME/.crm-cli). # CRM_CLI_BIN_DIR Where to symlink the `crm` binary (default: # $HOME/.local/bin). # # The proxy URL is baked into this script at deploy time. The API key is NOT # — each user pastes their own after install (gets it from whoever runs the # proxy). One token per user keeps revocation surgical. set -eu DEFAULT_BASE_URL="https://crm-cli.truly.you" BASE_URL="${CRM_CLI_BASE_URL:-$DEFAULT_BASE_URL}" INSTALL_DIR="${CRM_CLI_HOME:-$HOME/.crm-cli}" BIN_DIR="${CRM_CLI_BIN_DIR:-$HOME/.local/bin}" # Detect un-substituted bundle. We deliberately use a glob (not the literal # placeholder string) so the proxy's "replace https://crm-cli.truly.you → real URL" # substitution doesn't rewrite this check too. case "$BASE_URL" in PLACEHOLDER_*) echo "CRM_CLI_BASE_URL not set and no default baked into this script." >&2 echo "Set CRM_CLI_BASE_URL to your deploy URL and rerun." >&2 exit 1 ;; esac BASE_URL="${BASE_URL%/}" err() { printf "error: %s\n" "$*" >&2; exit 1; } info() { printf "→ %s\n" "$*"; } # Prereqs --------------------------------------------------------------------- command -v node >/dev/null 2>&1 || err "Node 18+ is required." NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0) [ "$NODE_MAJOR" -ge 18 ] 2>/dev/null || err "Node $NODE_MAJOR detected; need 18+." command -v curl >/dev/null 2>&1 || err "curl is required." command -v tar >/dev/null 2>&1 || err "tar is required." # Download + extract ---------------------------------------------------------- info "Downloading bundle from $BASE_URL/crm-cli.tar.gz" TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT INT TERM HTTP_CODE=$(curl -fsSL -o "$TMP/bundle.tar.gz" -w "%{http_code}" "$BASE_URL/crm-cli.tar.gz" || echo "0") [ -f "$TMP/bundle.tar.gz" ] || err "Download failed (HTTP $HTTP_CODE)." tar -xzf "$TMP/bundle.tar.gz" -C "$TMP" || err "Failed to extract bundle." # Install --------------------------------------------------------------------- info "Installing to $INSTALL_DIR" mkdir -p "$INSTALL_DIR" rm -rf "$INSTALL_DIR/bin" "$INSTALL_DIR/lib" "$INSTALL_DIR/mcp" "$INSTALL_DIR/scripts" cp -R "$TMP/." "$INSTALL_DIR/" chmod +x "$INSTALL_DIR/bin/crm.js" # Record install source so `crm update` can self-bootstrap later. printf "%s\n" "$BASE_URL" > "$INSTALL_DIR/.install-source" # Symlink --------------------------------------------------------------------- mkdir -p "$BIN_DIR" ln -sfn "$INSTALL_DIR/bin/crm.js" "$BIN_DIR/crm" # Seed config with the proxy URL so the user only has to paste their API key. CONFIG_DIR="$HOME/.config/crm-cli" CONFIG_FILE="$CONFIG_DIR/config.json" HAS_KEY="" if [ -f "$CONFIG_FILE" ]; then # Existing config — never overwrite. Detect if it already has a key. if grep -q '"apiKey"[^"]*"[^"]\{1,\}"' "$CONFIG_FILE" 2>/dev/null; then HAS_KEY=1 fi info "Existing config preserved: $CONFIG_FILE" else mkdir -p "$CONFIG_DIR" cat > "$CONFIG_FILE" <" echo echo "Then:" echo " crm members list" fi echo echo "Upgrade later with: crm update" echo echo "Wire up Claude Desktop MCP (uses your existing config — non-interactive):" echo " node $INSTALL_DIR/scripts/setup-claude-desktop.js" echo