-
Notifications
You must be signed in to change notification settings - Fork 26
feat(install): curl | sh one-liner installer for vouch-kb #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
876c9f7
5f27a1b
964e445
7c6536f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| name: install-sh | ||
|
|
||
| # Validates install.sh on every push that touches it: | ||
| # * shellcheck (lint) | ||
| # * POSIX-syntax check via dash | ||
| # * end-to-end smoke run on a fresh ubuntu-latest — installs the published | ||
| # vouch-kb wheel via pipx and verifies `vouch --version` | ||
| # | ||
| # The smoke run intentionally exercises the published PyPI artifact, not the | ||
| # in-repo source, so we catch installation breakage that doesn't show up in | ||
| # the regular pytest suite (e.g. a stale [web] extra reference). | ||
| # | ||
| # Supply-chain notes: this workflow pins third-party actions to their full | ||
| # commit SHA (the comment marks the human-readable tag). The rest of the | ||
| # repo's workflows still use tag pins — a sweep of ci.yml / release.yml / | ||
| # schema-check.yml to match this pattern is a worthwhile follow-up but | ||
| # kept out of this PR to avoid churning unrelated CI. | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main, release/*] | ||
| paths: | ||
| - "install.sh" | ||
| - ".github/workflows/install-sh.yml" | ||
| pull_request: | ||
| paths: | ||
| - "install.sh" | ||
| - ".github/workflows/install-sh.yml" | ||
| workflow_dispatch: | ||
|
|
||
| # Least-privilege at the workflow level; the smoke job needs nothing | ||
| # beyond reading the checked-out repo. Jobs that need more bump it up | ||
| # explicitly. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| lint: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| with: | ||
| # Don't leave the token sitting in .git/config after checkout — | ||
| # a leaked credentials file would otherwise allow pushing back. | ||
| persist-credentials: false | ||
|
|
||
| - name: shellcheck | ||
| run: | | ||
| sudo apt-get update -qq | ||
| sudo apt-get install -y shellcheck dash | ||
| shellcheck --version | ||
| shellcheck install.sh | ||
|
|
||
| - name: posix syntax (dash -n) | ||
| run: dash -n install.sh | ||
|
|
||
| smoke: | ||
| runs-on: ubuntu-latest | ||
| needs: lint | ||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: --help works | ||
| run: sh ./install.sh --help | ||
|
|
||
| - name: end-to-end install + smoke | ||
| run: | | ||
| set -e | ||
| sh ./install.sh --no-claude | ||
| # pipx's bin dir isn't necessarily on PATH for the verification | ||
| # step — re-export from pipx itself. Query via the pipx CLI: the | ||
| # runner ships pipx as a standalone binary that's on PATH but NOT | ||
| # importable as `python -m pipx`, so that form fails with | ||
| # "No module named pipx". Fall back to the conventional | ||
| # ~/.local/bin (pipx's default PIPX_BIN_DIR) if the query fails. | ||
| PIPX_BIN=$(pipx environment --value PIPX_BIN_DIR 2>/dev/null || true) | ||
| [ -n "$PIPX_BIN" ] || PIPX_BIN="$HOME/.local/bin" | ||
| export PATH="$PIPX_BIN:$PATH" | ||
| vouch --version | ||
| vouch capabilities | head -20 | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,286 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| #!/bin/sh | ||||||||||||||||||||||||||||||||||||||||||||||||
| # vouch installer — pipx-backed, no sudo. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||
| # curl -fsSL https://raw.githubusercontent.com/vouchdev/vouch/main/install.sh | sh | ||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||
| # What this does, in order: | ||||||||||||||||||||||||||||||||||||||||||||||||
| # 1. Pick a Python interpreter (>=3.11) — exits with a hint if none found. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # 2. Make sure pipx is installed (offers a user-scope install if missing). | ||||||||||||||||||||||||||||||||||||||||||||||||
| # 3. Install or upgrade the `vouch-kb` package via pipx. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # 4. Smoke-test: run `vouch --version` and report success. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # 5. If Claude Code config is detected, point you at `vouch install-mcp`. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Safe to re-run. Nothing requires sudo. No network calls beyond pipx's | ||||||||||||||||||||||||||||||||||||||||||||||||
| # normal PyPI fetch. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Flags: | ||||||||||||||||||||||||||||||||||||||||||||||||
| # --version <X.Y.Z> pin a vouch-kb version (default: latest) | ||||||||||||||||||||||||||||||||||||||||||||||||
| # --no-claude skip the Claude Code detection nudge | ||||||||||||||||||||||||||||||||||||||||||||||||
| # --quiet only print errors + the final summary | ||||||||||||||||||||||||||||||||||||||||||||||||
| # --help print this message | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| set -eu | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- knobs --------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| PKG_NAME="vouch-kb" | ||||||||||||||||||||||||||||||||||||||||||||||||
| PIN_VERSION="" | ||||||||||||||||||||||||||||||||||||||||||||||||
| SKIP_CLAUDE_CHECK=0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| QUIET=0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| MIN_PY_MAJOR=3 | ||||||||||||||||||||||||||||||||||||||||||||||||
| MIN_PY_MINOR=11 | ||||||||||||||||||||||||||||||||||||||||||||||||
| REPO_URL="https://github.com/vouchdev/vouch" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- pretty output ------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_BOLD=$(printf '\033[1m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_DIM=$(printf '\033[2m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_RED=$(printf '\033[31m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_GREEN=$(printf '\033[32m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_YELLOW=$(printf '\033[33m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_BLUE=$(printf '\033[34m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_RESET=$(printf '\033[0m') | ||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||
| C_BOLD=""; C_DIM=""; C_RED=""; C_GREEN=""; C_YELLOW=""; C_BLUE=""; C_RESET="" | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| info() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| [ "$QUIET" -eq 1 ] && return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s▸%s %s\n' "$C_BLUE" "$C_RESET" "$1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| warn() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s!%s %s\n' "$C_YELLOW" "$C_RESET" "$1" 1>&2 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| err() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s✗%s %s\n' "$C_RED" "$C_RESET" "$1" 1>&2 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ok() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| [ "$QUIET" -eq 1 ] && return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s✓%s %s\n' "$C_GREEN" "$C_RESET" "$1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| has_cmd() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command -v "$1" >/dev/null 2>&1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| usage() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| cat <<EOF | ||||||||||||||||||||||||||||||||||||||||||||||||
| ${C_BOLD}vouch installer${C_RESET} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| curl -fsSL https://raw.githubusercontent.com/vouchdev/vouch/main/install.sh | sh | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Usage: | ||||||||||||||||||||||||||||||||||||||||||||||||
| install.sh [--version X.Y.Z] [--no-claude] [--quiet] [--help] | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| What it does: | ||||||||||||||||||||||||||||||||||||||||||||||||
| 1. picks a python >=3.${MIN_PY_MINOR} | ||||||||||||||||||||||||||||||||||||||||||||||||
| 2. installs pipx (user scope) if missing | ||||||||||||||||||||||||||||||||||||||||||||||||
| 3. ${C_BOLD}pipx install ${PKG_NAME}${C_RESET} (or upgrade if already present) | ||||||||||||||||||||||||||||||||||||||||||||||||
| 4. smoke-tests with ${C_BOLD}vouch --version${C_RESET} | ||||||||||||||||||||||||||||||||||||||||||||||||
| 5. points you at Claude Code wiring if applicable | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Re-run safely. No sudo. No network beyond pipx + PyPI. | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Source: ${REPO_URL}/blob/main/install.sh | ||||||||||||||||||||||||||||||||||||||||||||||||
| EOF | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- arg parsing --------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| while [ $# -gt 0 ]; do | ||||||||||||||||||||||||||||||||||||||||||||||||
| case "$1" in | ||||||||||||||||||||||||||||||||||||||||||||||||
| --version) | ||||||||||||||||||||||||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||||||||||||||||||||||||
| [ $# -gt 0 ] || { err "--version needs a value"; exit 2; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| PIN_VERSION="$1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| --no-claude) | ||||||||||||||||||||||||||||||||||||||||||||||||
| SKIP_CLAUDE_CHECK=1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| --quiet) | ||||||||||||||||||||||||||||||||||||||||||||||||
| QUIET=1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| --help|-h) | ||||||||||||||||||||||||||||||||||||||||||||||||
| usage | ||||||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "unknown flag: $1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| usage | ||||||||||||||||||||||||||||||||||||||||||||||||
| exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- phase 1: pick a Python ---------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| pick_python() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Try, in order: python3.13, 3.12, 3.11, then bare python3. | ||||||||||||||||||||||||||||||||||||||||||||||||
| for cand in python3.13 python3.12 python3.11 python3; do | ||||||||||||||||||||||||||||||||||||||||||||||||
| if has_cmd "$cand"; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| ver=$("$cand" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null) || continue | ||||||||||||||||||||||||||||||||||||||||||||||||
| major=${ver%%.*} | ||||||||||||||||||||||||||||||||||||||||||||||||
| minor=${ver##*.} | ||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$major" -gt "$MIN_PY_MAJOR" ] || \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| { [ "$major" -eq "$MIN_PY_MAJOR" ] && [ "$minor" -ge "$MIN_PY_MINOR" ]; }; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s\n' "$cand" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- phase 2: ensure pipx ------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ensure_pipx_bin_on_path() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| # PIPX_BIN_DIR (typically ~/.local/bin) holds the `vouch` shim that pipx | ||||||||||||||||||||||||||||||||||||||||||||||||
| # writes. On many Ubuntu/Debian setups it's NOT on PATH by default even | ||||||||||||||||||||||||||||||||||||||||||||||||
| # when pipx itself is — the shim exists, smoke_test would still fail. | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Run this unconditionally so re-runs against a pre-installed pipx | ||||||||||||||||||||||||||||||||||||||||||||||||
| # still produce a usable shell. | ||||||||||||||||||||||||||||||||||||||||||||||||
| py="$1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| pipx_bin=$("$py" -m pipx environment --value PIPX_BIN_DIR 2>/dev/null || true) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -z "$pipx_bin" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| pipx_bin="$HOME/.local/bin" | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| case ":$PATH:" in | ||||||||||||||||||||||||||||||||||||||||||||||||
| *":$pipx_bin:"*) ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| *) PATH="$pipx_bin:$PATH"; export PATH ;; | ||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ensure_pipx() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| py="$1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| if has_cmd pipx; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| ok "pipx already installed ($(pipx --version 2>/dev/null | head -1))" | ||||||||||||||||||||||||||||||||||||||||||||||||
| ensure_pipx_bin_on_path "$py" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+160
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: git ls-files | grep -E "install\.sh$"Repository: vouchdev/vouch Length of output: 69 🏁 Script executed: wc -l install.shRepository: vouchdev/vouch Length of output: 73 🏁 Script executed: sed -n '130,220p' install.sh | cat -nRepository: vouchdev/vouch Length of output: 3573 🏁 Script executed: sed -n '155,210p' install.sh | cat -nRepository: vouchdev/vouch Length of output: 2156 🏁 Script executed: sed -n '1,50p' install.sh | cat -nRepository: vouchdev/vouch Length of output: 2020 🏁 Script executed: sed -n '195,210p' install.sh | cat -nRepository: vouchdev/vouch Length of output: 582 🏁 Script executed: # Check the main script flow to see how these functions are called
sed -n '230,264p' install.sh | cat -nRepository: vouchdev/vouch Length of output: 1367 Move PATH normalization outside the pre-existence check to ensure pipx's bin dir is always accessible. When Proposed fix ensure_pipx() {
py="$1"
- if has_cmd pipx; then
- ok "pipx already installed ($(pipx --version 2>/dev/null | head -1))"
- return 0
- fi
- info "pipx not found — installing into user site (no sudo)"
- if ! "$py" -m pip install --user --upgrade pipx >/dev/null 2>&1; then
- err "pip install --user pipx failed — try installing it manually"
- err " sudo apt install pipx # Debian/Ubuntu"
- err " brew install pipx # macOS"
- return 1
+ if has_cmd pipx; then
+ ok "pipx already installed ($(pipx --version 2>/dev/null | head -1))"
+ else
+ info "pipx not found — installing into user site (no sudo)"
+ if ! "$py" -m pip install --user --upgrade pipx >/dev/null 2>&1; then
+ err "pip install --user pipx failed — try installing it manually"
+ err " sudo apt install pipx # Debian/Ubuntu"
+ err " brew install pipx # macOS"
+ return 1
+ fi
+ "$py" -m pipx ensurepath >/dev/null 2>&1 || true
fi
- "$py" -m pipx ensurepath >/dev/null 2>&1 || true
- # `ensurepath` edits ~/.profile / ~/.zshrc to add pipx's bin dir, but the
- # current process's PATH is already fixed. Add it explicitly so the rest
- # of this script can find `vouch`.
- pipx_bin=$("$py" -m pipx environment --value PIPX_BIN_DIR 2>/dev/null || true)
+ # Always normalize current-process PATH to include pipx app binaries.
+ pipx_bin=$(pipx environment --value PIPX_BIN_DIR 2>/dev/null || "$py" -m pipx environment --value PIPX_BIN_DIR 2>/dev/null || true)
if [ -z "$pipx_bin" ]; then
pipx_bin="$HOME/.local/bin"
fi🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| info "pipx not found — installing into user site (no sudo)" | ||||||||||||||||||||||||||||||||||||||||||||||||
| if ! "$py" -m pip install --user --upgrade pipx >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "pip install --user pipx failed — try installing it manually" | ||||||||||||||||||||||||||||||||||||||||||||||||
| err " sudo apt install pipx # Debian/Ubuntu" | ||||||||||||||||||||||||||||||||||||||||||||||||
| err " brew install pipx # macOS" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| "$py" -m pipx ensurepath >/dev/null 2>&1 || true | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # `ensurepath` edits ~/.profile / ~/.zshrc to add pipx's bin dir, but | ||||||||||||||||||||||||||||||||||||||||||||||||
| # the current process's PATH is already fixed. Add it explicitly so | ||||||||||||||||||||||||||||||||||||||||||||||||
| # the rest of this script can find `vouch`. | ||||||||||||||||||||||||||||||||||||||||||||||||
| ensure_pipx_bin_on_path "$py" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if ! has_cmd pipx; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "pipx installed but not on PATH — restart your shell, then re-run" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| ok "pipx ready at $(command -v pipx)" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- phase 3: install vouch-kb ------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| install_vouch() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| target="$PKG_NAME" | ||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -n "$PIN_VERSION" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| target="${PKG_NAME}==${PIN_VERSION}" | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| already_installed=0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| if pipx list 2>/dev/null | grep -q "package $PKG_NAME"; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| already_installed=1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$already_installed" -eq 1 ] && [ -n "$PIN_VERSION" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| # --version was requested AND the package is already installed: | ||||||||||||||||||||||||||||||||||||||||||||||||
| # `pipx upgrade` would ignore the pin and pull latest. Force a | ||||||||||||||||||||||||||||||||||||||||||||||||
| # clean reinstall to the exact pin instead. | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "re-installing $target (honouring --version pin)" | ||||||||||||||||||||||||||||||||||||||||||||||||
| pipx install --force "$target" >/dev/null | ||||||||||||||||||||||||||||||||||||||||||||||||
| elif [ "$already_installed" -eq 1 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "upgrading existing $PKG_NAME" | ||||||||||||||||||||||||||||||||||||||||||||||||
| if ! pipx upgrade "$PKG_NAME" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| warn "pipx upgrade failed — falling back to reinstall" | ||||||||||||||||||||||||||||||||||||||||||||||||
| pipx install --force "$target" >/dev/null | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+191
to
+211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate and inspect install.sh
find . -name "install.sh" -type fRepository: vouchdev/vouch Length of output: 71 🏁 Script executed: # Read the install.sh file with context around lines 180-189
head -c 20000 install.sh | tail -c 15000 | cat -nRepository: vouchdev/vouch Length of output: 9894 🏁 Script executed: # Also search for PIN_VERSION and --version handling
rg "PIN_VERSION|--version" install.sh -B 2 -A 2Repository: vouchdev/vouch Length of output: 1590
When re-running the installer with Fix: Check for Proposed fix install_vouch() {
target="$PKG_NAME"
if [ -n "$PIN_VERSION" ]; then
target="${PKG_NAME}==${PIN_VERSION}"
fi
- if pipx list 2>/dev/null | grep -q "package $PKG_NAME"; then
+ if [ -n "$PIN_VERSION" ]; then
+ info "installing pinned $target"
+ pipx install --force "$target" >/dev/null
+ elif pipx list 2>/dev/null | grep -q "package $PKG_NAME"; then
info "upgrading existing $PKG_NAME"
if ! pipx upgrade --pip-args="" "$PKG_NAME" >/dev/null 2>&1; then
warn "pipx upgrade failed — falling back to reinstall"
pipx install --force "$target" >/dev/null
fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "installing $target" | ||||||||||||||||||||||||||||||||||||||||||||||||
| pipx install "$target" >/dev/null | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| ok "$PKG_NAME installed" | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- phase 4: smoke test -------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| smoke_test() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if ! has_cmd vouch; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "vouch command not found after install — check pipx's bin dir is on PATH" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| if ! ver=$(vouch --version 2>&1); then | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "vouch installed but \`vouch --version\` failed:" | ||||||||||||||||||||||||||||||||||||||||||||||||
| err " $ver" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| ok "$ver" | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- phase 5: Claude Code nudge ------------------------------------------ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| claude_code_nudge() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| [ "$SKIP_CLAUDE_CHECK" -eq 1 ] && return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # Detect Claude Code in the most non-invasive way: ~/.claude/ exists OR | ||||||||||||||||||||||||||||||||||||||||||||||||
| # the `claude` CLI is on PATH. Both are common. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -d "$HOME/.claude" ] || has_cmd claude; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '\n' | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "${C_BOLD}Claude Code detected.${C_RESET}" | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "Wire vouch into a project (one-time, per repo):" | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '\n' | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf ' %scd /path/to/your/project%s\n' "$C_BOLD" "$C_RESET" | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf ' %svouch init%s\n' "$C_BOLD" "$C_RESET" | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf ' %svouch install-mcp claude-code%s\n' "$C_BOLD" "$C_RESET" | ||||||||||||||||||||||||||||||||||||||||||||||||
| printf '\n' | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "Then restart Claude Code — vouch's kb.* tools and slash commands" | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "(/vouch-recall, /vouch-status, …) will be available." | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # --- main ---------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| main() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "${C_BOLD}vouch installer${C_RESET} ${C_DIM}($REPO_URL)${C_RESET}" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if ! py=$(pick_python); then | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "no Python >=$MIN_PY_MAJOR.$MIN_PY_MINOR found on PATH." | ||||||||||||||||||||||||||||||||||||||||||||||||
| err "Install Python first:" | ||||||||||||||||||||||||||||||||||||||||||||||||
| err " https://www.python.org/downloads/" | ||||||||||||||||||||||||||||||||||||||||||||||||
| err " brew install python@3.12 # macOS" | ||||||||||||||||||||||||||||||||||||||||||||||||
| err " sudo apt install python3.12 # Debian/Ubuntu 24.04+" | ||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| ok "python: $(command -v "$py")" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ensure_pipx "$py" || exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| install_vouch | ||||||||||||||||||||||||||||||||||||||||||||||||
| smoke_test || exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| printf '\n' | ||||||||||||||||||||||||||||||||||||||||||||||||
| info "${C_BOLD}Next:${C_RESET}" | ||||||||||||||||||||||||||||||||||||||||||||||||
| info " ${C_BOLD}vouch init${C_RESET} # create a .vouch/ KB in your project" | ||||||||||||||||||||||||||||||||||||||||||||||||
| info " ${C_BOLD}vouch serve${C_RESET} # start the MCP server" | ||||||||||||||||||||||||||||||||||||||||||||||||
| info " ${C_BOLD}vouch --help${C_RESET} # the rest" | ||||||||||||||||||||||||||||||||||||||||||||||||
| claude_code_nudge | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| printf '\n' | ||||||||||||||||||||||||||||||||||||||||||||||||
| ok "done. happy reviewing." | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| main | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: vouchdev/vouch
Length of output: 275
Harden workflow supply-chain controls: pin actions to commit SHAs, restrict token scope, and disable credential persistence.
The workflow uses mutable action versions (
v4,v5) and leaves default token permissions unset. Additionally,actions/checkout@v4does not explicitly disable credential persistence, increasing exposure if the workflow is compromised.Suggested hardening
Applies to: lines 1, 29–29, 45–47
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 1-64: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
🤖 Prompt for AI Agents
Source: Linters/SAST tools