| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| |
| |
|
|
| BOLD='\033[1m' |
| ACCENT='\033[38;2;251;191;36m' |
| INFO='\033[38;2;136;146;176m' |
| SUCCESS='\033[38;2;0;229;204m' |
| ERROR='\033[38;2;230;57;70m' |
| MUTED='\033[38;2;90;100;128m' |
| NC='\033[0m' |
|
|
| TAGLINE="12MB binary. Zero config. Accessibility-first browser control." |
|
|
| cleanup_tmpfiles() { |
| local f |
| for f in "${TMPFILES[@]:-}"; do |
| rm -rf "$f" 2>/dev/null || true |
| done |
| } |
| trap cleanup_tmpfiles EXIT |
|
|
| TMPFILES=() |
| LOGFILE="" |
|
|
| mktempfile() { |
| local f |
| f="$(mktemp)" |
| TMPFILES+=("$f") |
| echo "$f" |
| } |
|
|
| setup_logfile() { |
| LOGFILE="$(mktempfile)" |
| } |
|
|
| ui_info() { |
| local msg="$*" |
| echo -e "${MUTED}·${NC} ${msg}" |
| } |
|
|
| ui_success() { |
| local msg="$*" |
| echo -e "${SUCCESS}✓${NC} ${msg}" |
| } |
|
|
| ui_error() { |
| local msg="$*" |
| echo -e "${ERROR}✗${NC} ${msg}" |
| } |
|
|
| ui_section() { |
| local title="$1" |
| echo "" |
| echo -e "${ACCENT}${BOLD}${title}${NC}" |
| } |
|
|
| detect_os() { |
| OS="unknown" |
| if [[ "$OSTYPE" == "darwin"* ]]; then |
| OS="macos" |
| elif [[ "$OSTYPE" == "linux-gnu"* ]] || [[ -n "${WSL_DISTRO_NAME:-}" ]]; then |
| OS="linux" |
| fi |
|
|
| if [[ "$OS" == "unknown" ]]; then |
| ui_error "Unsupported operating system" |
| echo "This installer supports macOS and Linux (including WSL)." |
| exit 1 |
| fi |
|
|
| ui_success "Detected: $OS" |
| } |
|
|
| print_banner() { |
| echo -e "${ACCENT}${BOLD}" |
| echo " 🦀 Pinchtab Installer" |
| echo -e "${NC}${INFO} ${TAGLINE}${NC}" |
| echo "" |
| } |
|
|
| check_node() { |
| if ! command -v node &> /dev/null; then |
| ui_error "Node.js 18+ is required but not found" |
| echo "Install from https://nodejs.org or via your package manager" |
| exit 1 |
| fi |
|
|
| local version |
| version="$(node -v 2>/dev/null | cut -dv -f2 | cut -d. -f1)" |
| if [[ -z "$version" || "$version" -lt 18 ]]; then |
| ui_error "Node.js 18+ is required, but found $(node -v)" |
| exit 1 |
| fi |
|
|
| ui_success "Node.js $(node -v) found" |
| } |
|
|
| check_npm() { |
| if ! command -v npm &> /dev/null; then |
| ui_error "npm is required but not found" |
| exit 1 |
| fi |
| ui_success "npm $(npm -v) found" |
| } |
|
|
| check_chrome() { |
| local chrome_found=false |
| local chrome_path="" |
|
|
| |
| local candidates=( |
| "google-chrome" |
| "google-chrome-stable" |
| "chromium" |
| "chromium-browser" |
| "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" |
| "/Applications/Chromium.app/Contents/MacOS/Chromium" |
| "/usr/bin/google-chrome" |
| "/usr/bin/google-chrome-stable" |
| "/usr/bin/chromium" |
| "/usr/bin/chromium-browser" |
| ) |
|
|
| for candidate in "${candidates[@]}"; do |
| if command -v "$candidate" &> /dev/null || [[ -f "$candidate" ]]; then |
| chrome_found=true |
| chrome_path="$candidate" |
| break |
| fi |
| done |
|
|
| if [[ "$chrome_found" == true ]]; then |
| ui_success "Chrome/Chromium found: $chrome_path" |
| else |
| ui_error "Chrome or Chromium not found" |
| echo "" |
| echo "Pinchtab requires Chrome or Chromium to be installed." |
| echo "" |
| if [[ "$OS" == "macos" ]]; then |
| echo "Install Chrome or Chromium:" |
| echo " • Chrome: https://www.google.com/chrome/" |
| echo " • Chromium (via Homebrew): brew install chromium" |
| else |
| echo "Install Chrome or Chromium:" |
| echo "" |
| echo " Debian/Ubuntu/Raspberry Pi:" |
| echo " sudo apt update && sudo apt install -y chromium-browser" |
| echo "" |
| echo " Fedora/RHEL:" |
| echo " sudo dnf install -y chromium" |
| echo "" |
| echo " Arch Linux:" |
| echo " sudo pacman -S chromium" |
| echo "" |
| echo " Or download Chrome: https://www.google.com/chrome/" |
| fi |
| echo "" |
| echo "After installing Chrome/Chromium, run this installer again." |
| exit 1 |
| fi |
| } |
|
|
| install_pinchtab() { |
| ui_section "Installing Pinchtab" |
|
|
| if npm install -g pinchtab > "$LOGFILE" 2>&1; then |
| ui_success "Pinchtab installed successfully" |
| return 0 |
| else |
| local exit_code=$? |
| ui_error "npm install failed" |
| echo "" |
|
|
| |
| if grep -q "EACCES\|permission denied" "$LOGFILE"; then |
| echo "This is a permission error. Try one of these:" |
| echo "" |
| echo " Option 1: Use nvm (recommended)" |
| echo " curl https://github.com/nvm-sh/nvm/raw/master/install.sh | bash" |
| echo " nvm install node" |
| echo " npm install -g pinchtab" |
| echo "" |
| echo " Option 2: Use user prefix (no sudo needed)" |
| echo " npm install -g --prefix \"\$HOME/.local\" pinchtab" |
| echo " export PATH=\"\$HOME/.local/bin:\$PATH\" # Add to ~/.bashrc or ~/.zshrc" |
| echo "" |
| echo " Option 3: Fix npm permissions globally" |
| echo " mkdir ~/.npm-global" |
| echo " npm config set prefix '~/.npm-global'" |
| echo " export PATH=~/.npm-global/bin:\$PATH # Add to ~/.bashrc or ~/.zshrc" |
| echo "" |
| else |
| echo "Install log:" |
| cat "$LOGFILE" |
| fi |
| exit 1 |
| fi |
| } |
|
|
| verify_installation() { |
| if ! command -v pinchtab &> /dev/null; then |
| ui_error "Pinchtab binary not found in PATH after install" |
| echo "Try: npm install -g pinchtab" |
| exit 1 |
| fi |
|
|
| local version |
| version="$(pinchtab --version 2>/dev/null || echo 'unknown')" |
| ui_success "Pinchtab ready: $version" |
| } |
|
|
| show_next_steps() { |
| ui_section "Next steps" |
| echo "" |
| echo " Start the server:" |
| echo -e " ${MUTED}pinchtab${NC}" |
| echo "" |
| echo " In another terminal, test it:" |
| echo -e " ${MUTED}curl http://localhost:9867/health${NC}" |
| echo "" |
| echo " Or navigate & snapshot:" |
| echo -e " ${MUTED}pinchtab nav https://httpbin.org/html${NC}" |
| echo -e " ${MUTED}pinchtab snap${NC} (pipe to 'jq .count' if you have jq installed)" |
| echo "" |
| echo " Documentation:" |
| echo -e " ${MUTED}https://pinchtab.com${NC}" |
| echo "" |
| } |
|
|
| main() { |
| print_banner |
| setup_logfile |
|
|
| detect_os |
| check_node |
| check_npm |
| check_chrome |
| install_pinchtab |
| verify_installation |
| show_next_steps |
|
|
| ui_section "Installation complete!" |
| echo -e "Run ${ACCENT}${BOLD}pinchtab${NC} to start 🦀" |
| echo "" |
| } |
|
|
| main "$@" |
|
|