#!/usr/bin/env bash # Nisa CLI installer. Installs to ~/.local/bin by default — no sudo. # # One-liner: # curl -sSL https://api.rapidreview.io/cli/install | bash # # Pin a version: # NISA_VERSION=v0.3.0 curl -sSL https://api.rapidreview.io/cli/install | bash # # Install system-wide (prompts for sudo): # NISA_INSTALL_DIR=/usr/local/bin curl -sSL https://api.rapidreview.io/cli/install | bash # # The CLI was called `papyrus` before v0.3.0. The PAPYRUS_* env # overrides still work, and an existing `papyrus` binary in the install # dir is replaced with a symlink to `nisa` so old scripts keep working. set -euo pipefail BUCKET="${NISA_BUCKET:-${PAPYRUS_BUCKET:-rr_cli_releases}}" VERSION="${NISA_VERSION:-${PAPYRUS_VERSION:-latest}}" # Default to a user-writable path so `curl | bash` never prompts for # sudo on a fresh machine. Matches the rustup / deno / uv convention. # Override with NISA_INSTALL_DIR=/usr/local/bin for a system-wide # install (will prompt for sudo). INSTALL_DIR="${NISA_INSTALL_DIR:-${PAPYRUS_INSTALL_DIR:-$HOME/.local/bin}}" for cmd in curl tar uname; do command -v "$cmd" >/dev/null 2>&1 || { echo "error: required command not found: $cmd" >&2 exit 1 } done uname_s=$(uname -s) uname_m=$(uname -m) case "$uname_s/$uname_m" in Darwin/arm64) target="aarch64-apple-darwin" ;; Darwin/x86_64) target="x86_64-apple-darwin" ;; Linux/x86_64) target="x86_64-unknown-linux-musl" ;; Linux/aarch64|Linux/arm64) target="aarch64-unknown-linux-musl" ;; *) echo "error: unsupported platform $uname_s/$uname_m" >&2 echo "supported: Darwin/arm64, Darwin/x86_64, Linux/x86_64, Linux/aarch64" >&2 exit 1 ;; esac # Resolve `latest` → a concrete version. Cache-bust because GCS edge # caches can serve a stale VERSION for up to the object's max-age # (~1h) after a republish. if [[ "$VERSION" == "latest" ]]; then version_url="https://storage.googleapis.com/$BUCKET/latest/VERSION?t=$(date +%s)" if ! VERSION=$(curl -fsSL -H 'Cache-Control: no-cache' "$version_url"); then echo "error: failed to resolve latest nisa version" >&2 echo "version URL: $version_url" >&2 echo "detected platform: $uname_s/$uname_m ($target)" >&2 exit 1 fi fi archive="nisa-${VERSION}-${target}.tar.gz" url="https://storage.googleapis.com/$BUCKET/$VERSION/$archive" echo "> installing nisa $VERSION ($target)" tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT if ! curl -fsSL -o "$tmpdir/$archive" "$url"; then echo "error: failed to download nisa archive" >&2 echo "archive URL: $url" >&2 echo "detected platform: $uname_s/$uname_m ($target)" >&2 echo "requested version: $VERSION" >&2 exit 1 fi if curl -fsSL -o "$tmpdir/$archive.sha256" "$url.sha256"; then if command -v shasum >/dev/null 2>&1; then ( cd "$tmpdir" && shasum -a 256 -c "$archive.sha256" >/dev/null ) || { echo "error: checksum mismatch for $archive" >&2 exit 1 } elif command -v sha256sum >/dev/null 2>&1; then ( cd "$tmpdir" && sha256sum -c "$archive.sha256" >/dev/null ) || { echo "error: checksum mismatch for $archive" >&2 exit 1 } else echo "> warning: no checksum verifier found (shasum or sha256sum); skipping checksum" >&2 fi else echo "> warning: checksum unavailable: $url.sha256" >&2 fi if ! tar -xzf "$tmpdir/$archive" -C "$tmpdir"; then echo "error: failed to extract $archive" >&2 echo "archive URL: $url" >&2 exit 1 fi if [[ ! -f "$tmpdir/nisa" ]]; then echo "error: archive did not contain a nisa binary" >&2 echo "archive URL: $url" >&2 exit 1 fi chmod +x "$tmpdir/nisa" if ! "$tmpdir/nisa" --help >/dev/null; then echo "error: downloaded nisa binary does not run on this machine" >&2 echo "archive URL: $url" >&2 echo "detected platform: $uname_s/$uname_m ($target)" >&2 exit 1 fi actual_version=$("$tmpdir/nisa" --version) expected_version="nisa ${VERSION#v}" if [[ "$actual_version" != "$expected_version" ]]; then echo "error: downloaded binary version does not match the release" >&2 echo "expected: $expected_version" >&2 echo "actual: $actual_version" >&2 exit 1 fi # Anywhere under $HOME is guaranteed user-writable; skip the probe. # For system paths, try a silent mkdir first — it succeeds when the # user has rights and fails silently otherwise, at which point we fall # back to sudo. # # If a pre-rename `papyrus` sits in the install dir, replace it with a # relative symlink to `nisa`. Old muscle memory and agent scripts keep # working, and the stale binary stops nagging about upgrades. link_papyrus=0 [[ -e "$INSTALL_DIR/papyrus" || -L "$INSTALL_DIR/papyrus" ]] && link_papyrus=1 case "$INSTALL_DIR" in "$HOME"|"$HOME"/*) mkdir -p "$INSTALL_DIR" mv "$tmpdir/nisa" "$INSTALL_DIR/nisa" [[ $link_papyrus -eq 1 ]] && ln -sfn nisa "$INSTALL_DIR/papyrus" ;; *) if mkdir -p "$INSTALL_DIR" 2>/dev/null && [[ -w "$INSTALL_DIR" ]]; then mv "$tmpdir/nisa" "$INSTALL_DIR/nisa" [[ $link_papyrus -eq 1 ]] && ln -sfn nisa "$INSTALL_DIR/papyrus" else echo "> need sudo to write $INSTALL_DIR" sudo mkdir -p "$INSTALL_DIR" sudo mv "$tmpdir/nisa" "$INSTALL_DIR/nisa" [[ $link_papyrus -eq 1 ]] && sudo ln -sfn nisa "$INSTALL_DIR/papyrus" fi ;; esac # A pre-rename papyrus elsewhere on PATH can't be safely replaced from # here — point it out instead of leaving a stale binary to nag forever. if [[ $link_papyrus -eq 0 ]]; then stray=$(command -v papyrus 2>/dev/null || true) if [[ -n "$stray" ]]; then echo "> note: found an old papyrus binary at $stray — the CLI is now 'nisa'; remove the old one when convenient" fi fi on_path=0 case ":$PATH:" in *":$INSTALL_DIR:"*) on_path=1 ;; esac echo echo " installed: $INSTALL_DIR/nisa ($VERSION)" if [[ $link_papyrus -eq 1 ]]; then echo " linked: $INSTALL_DIR/papyrus -> nisa (the CLI was renamed)" fi if [[ $on_path -eq 1 ]]; then echo echo " Next step: run the command below to sign in —" echo echo " nisa" echo else # Covers the common $HOME/.local/bin case where the user's shell # hasn't been rebooted to pick it up. Give a copy-paste fix. echo echo " Note: $INSTALL_DIR is not on your PATH yet." echo " Add this to ~/.bashrc or ~/.zshrc and reopen the terminal:" echo echo " export PATH=\"$INSTALL_DIR:\$PATH\"" echo echo " Then run: nisa" echo fi