#!/usr/bin/env bash set -euo pipefail # Scout Terminal installer (public) # # curl -fsSL https://www.polarline.io/cli/install | bash # # Downloads the platform binary published to the public npm registry # (finny--) and installs it to ~/.finny/bin as `finny`. # No Node.js, npm, or postinstall scripts involved. # # Options / env: # --version Install a specific version (default: latest) # --no-modify-path Don't touch shell config files # FINNY_INSTALL_DIR Override install dir (default: $HOME/.finny/bin) APP=finny # Public npm identity for the open Scout Terminal CLI (NOT @finny-ai/finny-pro). WRAPPER_PKG="finny" REGISTRY="https://registry.npmjs.org" MUTED='\033[0;2m' RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' info() { echo -e "${MUTED}$1${NC}"; } error() { echo -e "${RED}$1${NC}" >&2; exit 1; } requested_version=${VERSION:-} no_modify_path=false while [[ $# -gt 0 ]]; do case "$1" in -v|--version) [[ -n "${2:-}" ]] || error "--version requires an argument" requested_version="$2"; shift 2 ;; --no-modify-path) no_modify_path=true; shift ;; -h|--help) sed -n '3,16p' "$0" 2>/dev/null || true; exit 0 ;; *) echo -e "${MUTED}Ignoring unknown option '$1'${NC}" >&2; shift ;; esac done command -v curl >/dev/null 2>&1 || error "'curl' is required but not installed." command -v tar >/dev/null 2>&1 || error "'tar' is required but not installed." # --- Platform detection ----------------------------------------------------- raw_os=$(uname -s) case "$raw_os" in Darwin*) os="darwin" ;; Linux*) os="linux" ;; MINGW*|MSYS*|CYGWIN*) error "Windows: install with 'npm i -g ${WRAPPER_PKG}' (PowerShell installer coming soon)." ;; *) error "Unsupported OS: $raw_os" ;; esac arch=$(uname -m) [[ "$arch" == "aarch64" ]] && arch="arm64" [[ "$arch" == "x86_64" ]] && arch="x64" # Detect Rosetta so Apple Silicon gets the native binary if [[ "$os" == "darwin" && "$arch" == "x64" ]]; then if [[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" == "1" ]]; then arch="arm64" fi fi case "$os-$arch" in darwin-arm64|darwin-x64|linux-x64|linux-arm64) ;; *) error "Unsupported platform: $os/$arch" ;; esac is_musl=false if [[ "$os" == "linux" ]]; then if [[ -f /etc/alpine-release ]] || (command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl); then is_musl=true fi fi needs_baseline=false if [[ "$arch" == "x64" ]]; then if [[ "$os" == "linux" ]] && ! grep -qwi avx2 /proc/cpuinfo 2>/dev/null; then needs_baseline=true fi if [[ "$os" == "darwin" && "$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)" != "1" ]]; then needs_baseline=true fi fi target="$os-$arch" [[ "$needs_baseline" == "true" ]] && target="$target-baseline" [[ "$is_musl" == "true" ]] && target="$target-musl" # --- Resolve version --------------------------------------------------------- json_field() { sed -n "s/.*\"$1\": *\"\([^\"]*\)\".*/\1/p" | head -1; } # Unscoped public packages: registry.npmjs.org/finny and finny-darwin-arm64, etc. pkg_url() { echo "$REGISTRY/$1"; } if [[ -z "$requested_version" ]]; then version=$(curl -fsSL "$(pkg_url "$WRAPPER_PKG")/latest" | json_field version) [[ -n "$version" ]] || error "Failed to resolve the latest version of '${WRAPPER_PKG}' from the npm registry." else version="${requested_version#v}" fi # Fetch per-target metadata; if the exact target isn't published, drop the # -baseline suffix (worst case the binary refuses to start on pre-AVX2 CPUs). pkg="$WRAPPER_PKG-$target" meta=$(curl -fsSL "$(pkg_url "$pkg")/$version" 2>/dev/null || true) if [[ -z "$meta" && "$target" == *"-baseline"* ]]; then target="${target/-baseline/}" pkg="$WRAPPER_PKG-$target" meta=$(curl -fsSL "$(pkg_url "$pkg")/$version" 2>/dev/null || true) fi [[ -n "$meta" ]] || error "Version $version is not published for $target (npm package: $pkg)." tarball=$(echo "$meta" | json_field tarball) integrity=$(echo "$meta" | json_field integrity) [[ -n "$tarball" ]] || error "Could not resolve download URL for $pkg@$version." # --- Skip if already installed ---------------------------------------------- INSTALL_DIR="${FINNY_INSTALL_DIR:-$HOME/.finny/bin}" # Prefer the binary in INSTALL_DIR over whatever happens to be first on PATH # (dev checkouts / npm globals can shadow ~/.finny/bin). if [[ -x "$INSTALL_DIR/$APP" ]]; then installed=$("$INSTALL_DIR/$APP" --version 2>/dev/null || echo "") if [[ "$installed" == "$version" ]]; then info "Scout Terminal $version is already installed at $INSTALL_DIR/$APP" exit 0 fi fi # --- Download, verify, install ----------------------------------------------- info "Installing Scout Terminal $version ($target) from npm:$pkg ..." tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/finny_install.XXXXXX") trap 'rm -rf "$tmp_dir"' EXIT if [[ -t 2 ]]; then curl -fL -# -o "$tmp_dir/pkg.tgz" "$tarball" else curl -fsSL -o "$tmp_dir/pkg.tgz" "$tarball" fi if [[ "$integrity" == sha512-* ]] && command -v openssl >/dev/null 2>&1; then actual="sha512-$(openssl dgst -sha512 -binary "$tmp_dir/pkg.tgz" | openssl base64 -A)" [[ "$actual" == "$integrity" ]] || error "Checksum mismatch, download corrupted or tampered with. Aborting." info "Checksum verified." fi tar -xzf "$tmp_dir/pkg.tgz" -C "$tmp_dir" [[ -f "$tmp_dir/package/bin/opencode" ]] || error "Unexpected package layout in $pkg@$version." mkdir -p "$INSTALL_DIR" mv "$tmp_dir/package/bin/opencode" "$INSTALL_DIR/$APP" chmod 755 "$INSTALL_DIR/$APP" # Keep a finny-pro alias for muscle memory / older docs, but the source package # is the public `finny` binary. ln -sf "$INSTALL_DIR/$APP" "$INSTALL_DIR/finny-pro" # --- PATH setup --------------------------------------------------------------- add_to_path() { local config_file=$1 command=$2 if grep -Fxq "$command" "$config_file" 2>/dev/null; then : elif [[ -w $config_file ]]; then printf '\n# scout\n%s\n' "$command" >> "$config_file" info "Added Scout to \$PATH in $config_file" else info "Add this to $config_file manually:" info " $command" fi } XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} current_shell=$(basename "${SHELL:-bash}") case $current_shell in fish) config_files="$HOME/.config/fish/config.fish" ;; zsh) config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc" ;; *) config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile" ;; esac if [[ "$no_modify_path" != "true" && ":$PATH:" != *":$INSTALL_DIR:"* ]]; then config_file="" for file in $config_files; do [[ -f $file ]] && { config_file=$file; break; } done if [[ -z $config_file ]]; then info "No shell config found. Add to PATH manually:" info " export PATH=$INSTALL_DIR:\$PATH" elif [[ $current_shell == "fish" ]]; then add_to_path "$config_file" "fish_add_path $INSTALL_DIR" else add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" fi fi if [[ "${GITHUB_ACTIONS:-}" == "true" && -n "${GITHUB_PATH:-}" ]]; then echo "$INSTALL_DIR" >> "$GITHUB_PATH" fi echo "" echo -e "${GREEN}█▀▀▀ █ █▀▀▄ █▀▀▄ █░░█${NC}" echo -e "${GREEN}█▀▀░ █ █░░█ █░░█ █▄▄█${NC}" echo -e "${GREEN}▀░░░ ▀ ▀░░▀ ▀░░▀ ▄▄▄█${NC}" echo "" info "Scout Terminal $version installed to $INSTALL_DIR/$APP" info "Source package: npm:$pkg@$version (public)" echo "" echo -e "Get started:" echo -e " export PATH=$INSTALL_DIR:\$PATH ${MUTED}# if needed${NC}" echo -e " finny ${MUTED}# then try: \"Build me a momentum-based ETH strategy\"${NC}" echo "" info "Docs: https://www.polarline.io/docs"