#!/usr/bin/env bash set -euo pipefail SKILL_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" say() { printf '\n\033[1m▶ %s\033[0m\n' "$*"; } warn() { printf '\033[33m! %s\033[0m\n' "$*"; } install_python_tools() { if ! command -v uv >/dev/null 2>&1; then warn "uv not installed. Install: https://docs.astral.sh/uv/getting-started/installation/" warn "Falling back to plain pip — tools will install into the active environment." if ! command -v pip >/dev/null 2>&1; then echo "Neither uv nor pip available. Aborting Python-tools install." exit 1 fi pip install bandit ruff mypy pip-audit else say "Installing Python tools into $SKILL_DIR/.venv/ via uv" uv sync --group tools fi } install_opengrep() { if [[ -x "$SKILL_DIR/.venv/bin/opengrep" ]]; then echo " opengrep: already installed (.venv/bin)" return fi say "Installing opengrep into $SKILL_DIR/.venv/bin/" local os arch asset os="$(uname -s)" arch="$(uname -m)" case "$os-$arch" in Linux-x86_64) asset="opengrep_manylinux_x86" ;; Linux-aarch64) asset="opengrep_manylinux_aarch64" ;; Darwin-x86_64) asset="opengrep_osx_x86" ;; Darwin-arm64) asset="opengrep_osx_arm64" ;; *) warn "opengrep: unsupported platform $os-$arch, install manually from https://github.com/opengrep/opengrep/releases" return ;; esac local release tag url # Buffer the response: piping curl into an early-exiting `grep -m1` makes curl die with (23), which pipefail turns fatal. release="$(curl -fsSL https://api.github.com/repos/opengrep/opengrep/releases/latest)" tag="$(grep -m1 '"tag_name"' <<<"$release" | sed -E 's/.*"([^"]+)".*/\1/')" if [[ -z "$tag" ]]; then warn "opengrep: could not resolve latest release tag, install manually" return fi url="https://github.com/opengrep/opengrep/releases/download/$tag/$asset" mkdir -p "$SKILL_DIR/.venv/bin" curl -fsSL "$url" -o "$SKILL_DIR/.venv/bin/opengrep" chmod +x "$SKILL_DIR/.venv/bin/opengrep" } install_powershell_modules() { if ! command -v pwsh >/dev/null 2>&1; then warn "pwsh not found — PowerShell review (PSScriptAnalyzer, InjectionHunter) will be skipped." warn " Install: https://learn.microsoft.com/powershell/scripting/install/installing-powershell" return fi say "Installing PowerShell modules for the current user" pwsh -NoProfile -NonInteractive -Command ' foreach ($m in "PSScriptAnalyzer", "InjectionHunter") { if (Get-Module -ListAvailable -Name $m) { Write-Host " ${m}: already installed" } else { Install-Module -Name $m -Scope CurrentUser -Force -AcceptLicense -Repository PSGallery Write-Host " ${m}: installed" } }' } install_native_brew() { say "Installing native tools via Homebrew" for pkg in gitleaks osv-scanner gh; do if brew list --formula | grep -qx "$pkg"; then echo " $pkg: already installed" else brew install "$pkg" fi done } install_native_apt() { say "Installing native tools via apt-get" if ! command -v gh >/dev/null 2>&1; then warn "gh: follow https://github.com/cli/cli/blob/trunk/docs/install_linux.md" fi if ! command -v gitleaks >/dev/null 2>&1; then warn "gitleaks: download from https://github.com/gitleaks/gitleaks/releases" fi if ! command -v osv-scanner >/dev/null 2>&1; then warn "osv-scanner: install via 'go install github.com/google/osv-scanner/cmd/osv-scanner@latest' or download from https://github.com/google/osv-scanner/releases" fi } install_native_arch() { local helper if command -v paru >/dev/null 2>&1; then helper="paru" elif command -v yay >/dev/null 2>&1; then helper="yay" else helper="pacman" fi say "Installing native tools via $helper" # --needed still invokes sudo, so skip the helper entirely when nothing is missing. local pkgs=() pkg for pkg in gitleaks github-cli osv-scanner; do pacman -Q "$pkg" >/dev/null 2>&1 || pkgs+=("$pkg") done if [[ ${#pkgs[@]} -eq 0 ]]; then echo " gitleaks, github-cli, osv-scanner: already installed" return fi if [[ "$helper" == "pacman" ]]; then sudo pacman -S --needed --noconfirm gitleaks github-cli || true if ! command -v osv-scanner >/dev/null 2>&1; then warn "osv-scanner is AUR-only; pacman can't install it. Use paru/yay or install manually:" warn " go install github.com/google/osv-scanner/cmd/osv-scanner@latest" fi else "$helper" -S --needed --noconfirm "${pkgs[@]}" fi } install_native() { if command -v brew >/dev/null 2>&1; then install_native_brew elif command -v pacman >/dev/null 2>&1; then install_native_arch elif command -v apt-get >/dev/null 2>&1; then install_native_apt else warn "No supported native package manager found (brew/pacman/apt). Install gitleaks, osv-scanner, gh manually." fi } # Checked in .venv/bin only: each plugin version gets its own venv, and a copy on PATH says nothing about this one. VENV_TOOLS=(bandit ruff mypy pip-audit vulture radon interrogate lizard opengrep) NATIVE_TOOLS=(gitleaks osv-scanner gh) verify_tools() { say "Verifying tool availability" local tool missing=() for tool in "${VENV_TOOLS[@]}"; do if [[ -x "$SKILL_DIR/.venv/bin/$tool" ]]; then printf ' %-15s %s\n' "$tool" "(.venv/bin)" else missing+=("$tool") fi done for tool in "${NATIVE_TOOLS[@]}" pwsh; do if command -v "$tool" >/dev/null 2>&1; then printf ' %-15s %s\n' "$tool" "$(command -v "$tool")" elif [[ "$tool" == pwsh ]]; then printf ' %-15s %s\n' "$tool" "missing (optional: PowerShell review only)" else missing+=("$tool") fi done [[ ${#missing[@]} -eq 0 ]] && return printf ' %-15s \033[31mmissing\033[0m\n' "${missing[@]}" return 1 } main() { local user_only=0 case "${1:-}" in "") ;; --check-only) verify_tools; return ;; --user-only) user_only=1 ;; *) echo "usage: install-tools.sh [--check-only | --user-only]" >&2; return 2 ;; esac cd "$SKILL_DIR" install_python_tools install_opengrep install_powershell_modules if [[ $user_only -eq 1 ]]; then warn "--user-only: skipped system packages (gitleaks, osv-scanner, gh). Re-run without it to install them." else install_native fi cat <