Skip to content

feat(install): curl | sh one-liner installer for vouch-kb#216

Merged
plind-junior merged 4 commits into
testfrom
feat/install-script
Jun 17, 2026
Merged

feat(install): curl | sh one-liner installer for vouch-kb#216
plind-junior merged 4 commits into
testfrom
feat/install-script

Conversation

@plind-junior

@plind-junior plind-junior commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

curl -fsSL https://raw.githubusercontent.com/vouchdev/vouch/main/install.sh | sh

a posix sh script (shellcheck-clean, dash-tested) that:

  • picks a python >= 3.11 from common candidates (python3.13/3.12/3.11/python3)
  • installs pipx into user site if missing, no sudo
  • pipx install vouch-kb (or upgrade-in-place when already present)
  • smoke-tests with vouch --version
  • detects ~/.claude or the claude CLI on PATH and points at vouch install-mcp claude-code for the per-project wiring step

flags: --version X.Y.Z to pin, --no-claude to skip the nudge, --quiet, --help. honours NO_COLOR. re-runnable.

ci workflow .github/workflows/install-sh.yml runs shellcheck + a dash syntax check + an end-to-end smoke install on ubuntu-latest that actually exercises the published pypi wheel — catches install-path breakage that the regular pytest run misses.

readme install section updated to lead with the one-liner.

What changed

Why

What might break

VEP

Tests

  • make check passes locally (lint + mypy + pytest)
  • New / changed behaviour has a test
  • CHANGELOG.md updated under ## [Unreleased]

Summary by CodeRabbit

  • New Features
    • Added simplified one-liner installation method for Linux/macOS using shell script
    • Installer automatically detects Python, sets up dependencies, and installs the package
    • Added smoke tests to verify installation success
    • Automatic Claude Code integration detection included

  curl -fsSL https://raw.githubusercontent.com/vouchdev/vouch/main/install.sh | sh

a posix sh script (shellcheck-clean, dash-tested) that:

* picks a python >= 3.11 from common candidates (python3.13/3.12/3.11/python3)
* installs pipx into user site if missing, no sudo
* `pipx install vouch-kb` (or upgrade-in-place when already present)
* smoke-tests with `vouch --version`
* detects ~/.claude or the `claude` CLI on PATH and points at
  `vouch install-mcp claude-code` for the per-project wiring step

flags: --version X.Y.Z to pin, --no-claude to skip the nudge, --quiet,
--help. honours NO_COLOR. re-runnable.

ci workflow .github/workflows/install-sh.yml runs shellcheck + a dash
syntax check + an end-to-end smoke install on ubuntu-latest that
actually exercises the published pypi wheel — catches install-path
breakage that the regular pytest run misses.

readme install section updated to lead with the one-liner.
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f1820747-6ff7-4d8d-a551-7214f5a29e51

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds install.sh, a 264-line POSIX sh script that selects a Python ≥3.11 interpreter, installs pipx if absent, installs vouch-kb via pipx, runs a smoke test, and optionally prints Claude Code wiring commands. Updates README install instructions with a curl | sh one-liner. Adds a CI workflow to lint the script with shellcheck/dash -n and run an end-to-end install smoke test.

Changes

POSIX sh installer, README, and CI

Layer / File(s) Summary
Script header, helpers, and CLI parsing
README.md, install.sh
README install section gains a curl | sh one-liner with explanatory text. install.sh adds the header doc, set -eu strict mode, terminal-aware color variables, message/detection helper functions (info, warn, error, success, has_cmd, usage), and argument parsing for --version, --no-claude, --quiet, and --help.
Python selection, pipx setup, install, smoke test, and main orchestration
install.sh
pick_python() probes python3.13python3.12python3.11python3 against a sys.version_info check. ensure_pipx() installs pipx via user pip if missing and prepends its bin dir to PATH. install_vouch() runs pipx install or pipx upgrade for vouch-kb with optional version pin. smoke_test() asserts vouch --version succeeds. claude_code_nudge() detects ~/.claude or claude CLI and prints wiring commands. main() sequences all steps with user-facing status messages.
GitHub Actions lint and smoke workflow
.github/workflows/install-sh.yml
Adds the install-sh workflow with a lint job (shellcheck + dash -n) and a smoke job that runs sh ./install.sh --no-claude, resolves the pipx bin dir, and verifies vouch --version and vouch capabilities.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant installer as install.sh
    participant Python as Python 3.11+
    participant pipx
    participant vouch

    User->>installer: curl .../install.sh | sh
    installer->>Python: probe python3.13 through python3
    Python-->>installer: interpreter path
    installer->>pipx: check PATH, install via pip --user if absent
    pipx-->>installer: ready, bin dir prepended to PATH
    installer->>pipx: pipx install or upgrade vouch-kb
    pipx-->>installer: vouch-kb installed
    installer->>vouch: vouch --version smoke test
    vouch-->>installer: version string
    installer->>User: next steps and optional Claude Code wiring nudge
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop, hop, a shell script appears,
Picks Python, prods pipx, then cheers!
curl | sh in one bound,
Vouch-kb is found,
And Claude gets wired up with no fears! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a curl-pipe-sh installer for vouch-kb, which is the primary focus across the three modified files (install.sh, README.md, and the new CI workflow).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/install-script

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@plind-junior plind-junior changed the base branch from main to test June 16, 2026 10:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/install-sh.yml:
- Line 1: Harden the GitHub Actions workflow supply chain security by making
three changes: First, pin all action references from mutable versions (such as
v4, v5) to specific commit SHAs instead of version tags throughout the workflow.
Second, add an explicit permissions block at the workflow level to restrict the
default GITHUB_TOKEN scope to only the minimum required permissions. Third,
locate the actions/checkout action and add the persist-credentials: false
parameter to disable credential persistence and reduce exposure if the workflow
is compromised.

In `@install.sh`:
- Around line 180-189: The script currently constructs the `target` variable
with the pinned version but then ignores it when upgrading an existing package,
causing the `--version` flag to be disregarded. Restructure the conditional
logic to check `PIN_VERSION` first: if PIN_VERSION is set, always perform a
pinned install with the `target` variable using pipx install --force, bypassing
the upgrade path entirely. Otherwise, if the package already exists (when
PIN_VERSION is not set), proceed with the upgrade attempt; if upgrade fails or
the package doesn't exist, fall back to a fresh install without a version pin.
- Around line 143-146: The early return statement in the has_cmd pipx check
prevents the PATH normalization for PIPX_BIN_DIR from executing when pipx is
already installed, making vouch unreachable during smoke_test. Move the code
that normalizes PIPX_BIN_DIR in the current process (typically involving PATH
updates) to execute before the early return in the has_cmd pipx conditional
block, ensuring the PATH is always updated to include pipx's bin directory
regardless of whether pipx pre-exists on the system.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d0f27d8a-d32b-4ac3-9b6a-7f26f17a96ff

📥 Commits

Reviewing files that changed from the base of the PR and between 3beb821 and 876c9f7.

📒 Files selected for processing (3)
  • .github/workflows/install-sh.yml
  • README.md
  • install.sh

@@ -0,0 +1,63 @@
name: install-sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unpinned actions and missing workflow hardening in this file.
rg -n '^\s*uses:\s*[^@]+@v[0-9]+' .github/workflows/install-sh.yml
rg -n '^\s*permissions:' .github/workflows/install-sh.yml
rg -n 'actions/checkout@' .github/workflows/install-sh.yml -A3 -B1

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@v4 does not explicitly disable credential persistence, increasing exposure if the workflow is compromised.

Suggested hardening
 name: install-sh
+permissions:
+  contents: read
@@
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<pinned-commit-sha>
+        with:
+          persist-credentials: false
@@
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<pinned-commit-sha>
+        with:
+          persist-credentials: false
 
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@<pinned-commit-sha>

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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/install-sh.yml at line 1, Harden the GitHub Actions
workflow supply chain security by making three changes: First, pin all action
references from mutable versions (such as v4, v5) to specific commit SHAs
instead of version tags throughout the workflow. Second, add an explicit
permissions block at the workflow level to restrict the default GITHUB_TOKEN
scope to only the minimum required permissions. Third, locate the
actions/checkout action and add the persist-credentials: false parameter to
disable credential persistence and reduce exposure if the workflow is
compromised.

Source: Linters/SAST tools

Comment thread install.sh
Comment on lines +143 to +146
if has_cmd pipx; then
ok "pipx already installed ($(pipx --version 2>/dev/null | head -1))"
return 0
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "install\.sh$"

Repository: vouchdev/vouch

Length of output: 69


🏁 Script executed:

wc -l install.sh

Repository: vouchdev/vouch

Length of output: 73


🏁 Script executed:

sed -n '130,220p' install.sh | cat -n

Repository: vouchdev/vouch

Length of output: 3573


🏁 Script executed:

sed -n '155,210p' install.sh | cat -n

Repository: vouchdev/vouch

Length of output: 2156


🏁 Script executed:

sed -n '1,50p' install.sh | cat -n

Repository: vouchdev/vouch

Length of output: 2020


🏁 Script executed:

sed -n '195,210p' install.sh | cat -n

Repository: 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 -n

Repository: vouchdev/vouch

Length of output: 1367


Move PATH normalization outside the pre-existence check to ensure pipx's bin dir is always accessible.

When pipx already exists on the system, the function returns early before normalizing PIPX_BIN_DIR in the current process. This leaves vouch unreachable when smoke_test() runs, causing the installation to fail even though vouch was successfully installed.

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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` around lines 143 - 146, The early return statement in the has_cmd
pipx check prevents the PATH normalization for PIPX_BIN_DIR from executing when
pipx is already installed, making vouch unreachable during smoke_test. Move the
code that normalizes PIPX_BIN_DIR in the current process (typically involving
PATH updates) to execute before the early return in the has_cmd pipx conditional
block, ensuring the PATH is always updated to include pipx's bin directory
regardless of whether pipx pre-exists on the system.

Comment thread install.sh
Comment on lines +180 to +189
if [ -n "$PIN_VERSION" ]; then
target="${PKG_NAME}==${PIN_VERSION}"
fi

if 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and inspect install.sh
find . -name "install.sh" -type f

Repository: 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 -n

Repository: 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 2

Repository: vouchdev/vouch

Length of output: 1590


--version pin is ignored when vouch-kb is already installed.

When re-running the installer with --version X.Y.Z on an already-installed package, the script upgrades to the latest version instead of respecting the pin. The target variable (containing vouch-kb==X.Y.Z) is only used if the upgrade fails, breaking the documented --version behavior.

Fix: Check for PIN_VERSION first. If set, always perform a pinned install; otherwise, upgrade existing or install fresh.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -n "$PIN_VERSION" ]; then
target="${PKG_NAME}==${PIN_VERSION}"
fi
if 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
if [ -n "$PIN_VERSION" ]; then
target="${PKG_NAME}==${PIN_VERSION}"
fi
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
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` around lines 180 - 189, The script currently constructs the
`target` variable with the pinned version but then ignores it when upgrading an
existing package, causing the `--version` flag to be disregarded. Restructure
the conditional logic to check `PIN_VERSION` first: if PIN_VERSION is set,
always perform a pinned install with the `target` variable using pipx install
--force, bypassing the upgrade path entirely. Otherwise, if the package already
exists (when PIN_VERSION is not set), proceed with the upgrade attempt; if
upgrade fails or the package doesn't exist, fall back to a fresh install without
a version pin.

three findings, all real:

1. workflow supply chain — install-sh.yml now pins both third-party
   actions to commit SHAs (actions/checkout v4.2.2, setup-python v5.3.0)
   with human-readable tags in the comment, sets workflow-level
   `permissions: contents: read`, and disables credential persistence on
   every checkout. note in the file flags that ci.yml / release.yml /
   schema-check.yml use the older tag-pin convention; sweeping them is
   a worthwhile follow-up but kept out of this pr to avoid churn.

2. PATH normalization — the previous `ensure_pipx` returned early when
   pipx was already installed, so the PIPX_BIN_DIR normalization that
   guarantees `vouch` is findable never ran. extracted into
   `ensure_pipx_bin_on_path` and called unconditionally. on ubuntu where
   pipx ships in apt but ~/.local/bin isn't on PATH, the smoke step
   would have failed without this.

3. --version pin honoured on re-runs — when the package was already
   installed AND --version X.Y.Z was passed, the previous code called
   `pipx upgrade` (which ignores the pin) instead of pinning to the
   requested version. now: if pinned, force-reinstall to the exact
   version; if unpinned, upgrade in place.

local validation:
  shellcheck install.sh            → clean
  dash -n install.sh               → posix-clean
  bash -n install.sh               → bash-clean
  ./install.sh --help              → still works
  ./install.sh --bogus             → exits 2 with clean error
The smoke job's verification step queried PIPX_BIN_DIR with `python -m pipx environment`, but GitHub runners ship pipx as a standalone binary that is on PATH yet not importable in the setup-python interpreter, so the call died with "No module named pipx" and failed the job under set -e (install.sh itself already tolerates this). Query via the pipx CLI instead and fall back to ~/.local/bin.
@plind-junior plind-junior merged commit 9396e15 into test Jun 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant