#!/usr/bin/env bash # PrimeRouter — Claude Code CLI install + configure script. # - Installs Node + @anthropic-ai/claude-code if missing (with confirmation). # - Self-contained network-wise: only needs the npm registry (with automatic # mirror fallback) and PrimeRouter itself — no other install endpoints. # - Falls back to a per-user npm prefix when the global one is not writable, # so it never needs sudo. # - Writes Claude Code base URL + auth pointing at https://primerouter.ai. # - Backs up the shell profile before edit. # - Reads the PrimeRouter API key interactively (never hard-coded here). set -euo pipefail YELLOW='\033[1;33m'; GREEN='\033[1;32m'; RED='\033[1;31m'; NC='\033[0m' say() { printf "%b%s%b\n" "$1" "$2" "$NC"; } ok() { say "$GREEN" "✓ $1"; } warn() { say "$YELLOW" "! $1"; } die() { say "$RED" "✗ $1"; exit 1; } confirm() { local prompt="$1" reply # Set by one-click installer wrappers to run unattended. if [[ "${PRIMEROUTER_AUTO_CONFIRM:-}" = "1" ]]; then return 0; fi printf "%s [y/N] " "$prompt" read -r reply /dev/null) || return 1 [[ -n "$prefix" ]] || return 1 for d in "${prefix}/lib/node_modules" "${prefix}/bin"; do while [[ ! -e "$d" ]]; do d=$(dirname "$d"); done [[ -w "$d" ]] || return 1 done } # Prefers the mirror registry (consistently fast from our users' networks; # the default registry is often reachable but too slow) and falls back to # the default registry when the mirror is unreachable. Respects a registry # the user already configured in .npmrc. NPM_REGISTRY_FLAG="" choose_npm_registry() { local configured configured=$(npm config get registry 2>/dev/null || echo "") case "$configured" in ""|"${NPM_DEFAULT_REGISTRY}"|"${NPM_DEFAULT_REGISTRY}/") ;; *) return ;; # custom registry already configured — leave it alone esac if curl -fsSL --max-time 8 -o /dev/null "${NPM_MIRROR_REGISTRY}/-/ping" 2>/dev/null; then NPM_REGISTRY_FLAG="--registry=${NPM_MIRROR_REGISTRY}" else warn "${NPM_MIRROR_REGISTRY} is unreachable from this network; using the default npm registry." fi } # Shell profile that new terminals actually read (macOS terminals start login # shells, which read .bash_profile, not .bashrc). detect_profile() { case "${SHELL:-}" in */zsh) echo "${HOME}/.zshrc" ;; */bash) if [[ "$(uname -s)" == "Darwin" ]]; then echo "${HOME}/.bash_profile" else echo "${HOME}/.bashrc" fi ;; *) echo "" ;; esac } # Persists the per-user npm prefix on PATH. Block markers are shared by all # PrimeRouter install scripts so re-runs and other tools stay deduplicated. persist_user_prefix_path() { local profile path_line path_line="export PATH=\"${USER_NPM_PREFIX}/bin:\$PATH\"" profile=$(detect_profile) if [[ -z "$profile" ]]; then warn "Unknown shell. Add this line to your shell profile manually:" printf " %s\n" "$path_line" return fi backup_if_exists "$profile" if [[ -f "$profile" ]]; then sed -i.tmp '/# >>> PrimeRouter npm prefix >>>/,/# <<< PrimeRouter npm prefix <<>> PrimeRouter npm prefix >>>\n' printf '%s\n' "$path_line" printf '# <<< PrimeRouter npm prefix <<<\n' } >> "$profile" ok "Added ${USER_NPM_PREFIX}/bin to PATH in ${profile}" } # npm install -g with automatic fallback to a per-user prefix when the global # one is not writable — never needs sudo. npm_install_global() { local pkg="$1" bin_name="$2" choose_npm_registry if npm_global_writable; then npm install -g ${NPM_REGISTRY_FLAG} "$pkg" \ || die "npm install failed. Check the npm output above, then re-run this script." else warn "npm's global directory ($(npm prefix -g)) is not writable by this user." warn "Installing into ${USER_NPM_PREFIX} instead (no sudo needed)." mkdir -p "${USER_NPM_PREFIX}" NPM_CONFIG_PREFIX="${USER_NPM_PREFIX}" npm install -g ${NPM_REGISTRY_FLAG} "$pkg" \ || die "npm install failed. Check the npm output above, then re-run this script." export PATH="${USER_NPM_PREFIX}/bin:${PATH}" persist_user_prefix_path fi command -v "$bin_name" >/dev/null 2>&1 \ || die "Install finished but '${bin_name}' is still not on PATH. Open a new terminal and re-run this script." ok "Installed ${pkg}." } # --- End shared npm install hardening --- # --- 1. Node.js --- if ! command -v node >/dev/null 2>&1 || [[ "$(node -v | sed -E 's/^v([0-9]+).*/\1/')" -lt 22 ]]; then warn "Node.js v22+ is required to install @anthropic-ai/claude-code." confirm "Install Node.js LTS now?" \ || die "Aborted. Install Node.js manually then re-run." curl -fsSL "${INSTALL_BASE}/nodejs.sh" | bash fi # --- 2. claude-code CLI --- if ! command -v claude >/dev/null 2>&1; then warn "Claude Code CLI (@anthropic-ai/claude-code) is not on PATH." confirm "Install it via npm now?" \ || die "Aborted. Install @anthropic-ai/claude-code manually then re-run." npm_install_global "@anthropic-ai/claude-code" "claude" fi # --- 3. API key --- # Pre-seeded by personalized installers; prompt only when absent. PR_API_KEY="${PRIMEROUTER_API_KEY:-}" if [[ -z "$PR_API_KEY" ]]; then printf "Paste your PrimeRouter API key (input hidden): " read -rs PR_API_KEY >> PrimeRouter for Claude Code >>>/,/# <<< PrimeRouter for Claude Code <<> "$profile" <>> PrimeRouter for Claude Code >>> export ANTHROPIC_BASE_URL="${PR_BASE_URL}" export ANTHROPIC_AUTH_TOKEN="${PR_API_KEY}" # Skip non-essential requests (telemetry, error reporting, update checks). export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 # <<< PrimeRouter for Claude Code <<< EOF ok "Added ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN to ${profile}" else warn "Unknown shell. Export these manually before running 'claude':" printf " export ANTHROPIC_BASE_URL=%s\n export ANTHROPIC_AUTH_TOKEN=\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1\n" "$PR_BASE_URL" fi # Also export for the current shell so the next command works. export ANTHROPIC_BASE_URL="$PR_BASE_URL" export ANTHROPIC_AUTH_TOKEN="$PR_API_KEY" export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 ok "Claude Code is wired to ${PR_BASE_URL}. Open a new terminal then run: claude"