From f060577e2cce56e64c451e774ec0302f08d8661d Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 27 Jul 2026 14:06:14 +0200 Subject: [PATCH 1/5] docs(scripts): design upstream Byobu and Trustmux installers Signed-off-by: Sebastian Mendel --- ...-07-27-byobu-trustmux-installers-design.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-27-byobu-trustmux-installers-design.md diff --git a/docs/superpowers/specs/2026-07-27-byobu-trustmux-installers-design.md b/docs/superpowers/specs/2026-07-27-byobu-trustmux-installers-design.md new file mode 100644 index 0000000..87f9570 --- /dev/null +++ b/docs/superpowers/specs/2026-07-27-byobu-trustmux-installers-design.md @@ -0,0 +1,58 @@ +# Byobu and Trustmux Upstream Installers + +## Goal + +Add Byobu and Trustmux to the CLI tool catalog without relying on outdated +distribution packages. Both tools must install into the user's home directory, +participate in version auditing, and leave system-managed installations intact. + +## Byobu + +Byobu does not publish a standalone executable release. A dedicated installer +will therefore: + +1. Resolve the newest stable tag from `dustinkirkland/byobu`. +2. Download the corresponding GitHub source archive with HTTP failure checks + and bounded retries. +3. Build from the release source using `autogen.sh` when required, followed by + `configure --prefix="$HOME/.local"`, `make`, and user-local installation. +4. Verify the installed command directly below `~/.local/bin` rather than + relying on `PATH`. +5. Support `install`, `update`, and `uninstall` actions and refresh the local + audit snapshot after successful installation. + +Build logs must be retained until cleanup and their final lines printed when a +step fails. Temporary files must be removed on normal exit and interruption. + +## Trustmux + +Trustmux is officially published on PyPI. Its catalog entry will use the +existing `uv_tool` installer with package name and binary name `trustmux`. +This provides the newest stable published version as a user-local executable +without using the Ubuntu PPA or the Byobu distribution package. + +## Catalog and audit + +Each tool receives a catalog entry with its official homepage, upstream source, +binary name, version command, category, and installation method. The committed +upstream baseline will contain both tools. Catalog documentation counts will be +updated to match the resulting number of entries. + +## Testing + +Catalog tests will verify: + +- both entries exist and contain the intended installation methods; +- Byobu points to its dedicated installer; +- Trustmux maps to the PyPI package and `trustmux` executable; +- all catalog JSON remains valid. + +The dedicated Byobu installer must pass Bash syntax checks and Shellcheck. The +catalog test module and full Python test suite must remain green. + +## Out of scope + +- Installing development snapshots from a default branch. +- Replacing or deleting distribution-managed Byobu or Trustmux packages. +- Starting, enabling, pairing, or configuring the Trustmux daemon. +- Enabling Byobu automatically at login. From d5e8ff10d9c51219f2fac194bccd023fcd3d8f7a Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 27 Jul 2026 14:24:13 +0200 Subject: [PATCH 2/5] docs(scripts): plan upstream Byobu and Trustmux installers Signed-off-by: Sebastian Mendel --- .../2026-07-27-byobu-trustmux-installers.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-27-byobu-trustmux-installers.md diff --git a/docs/superpowers/plans/2026-07-27-byobu-trustmux-installers.md b/docs/superpowers/plans/2026-07-27-byobu-trustmux-installers.md new file mode 100644 index 0000000..41f307e --- /dev/null +++ b/docs/superpowers/plans/2026-07-27-byobu-trustmux-installers.md @@ -0,0 +1,68 @@ +# Byobu and Trustmux Upstream Installers Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add audited, user-local installations of the latest stable Byobu and Trustmux releases without using distribution packages. + +**Architecture:** Byobu uses a dedicated Bash installer because upstream publishes source tags rather than a standalone binary; it selects only numeric stable tags, builds the tagged archive, and installs under `~/.local`. Trustmux uses the existing generic `uv_tool` installer and its official PyPI package. Both catalog entries feed the existing GitHub/PyPI collectors and snapshot model. + +**Tech Stack:** Bash, GitHub REST API/source archives, Autoconf/Automake/Make, JSON catalog metadata, uv/PyPI, pytest. + +--- + +## Task 1: Specify catalog and installer behavior with failing tests + +**Files:** +- Modify: `tests/test_catalog_and_collectors.py` +- Test: `tests/test_catalog_and_collectors.py` + +- [ ] Add `TestByobuCatalog` assertions for `catalog/byobu.json`, `name == "byobu"`, `install_method == "dedicated_script"`, `script == "install_byobu.sh"`, `github_repo == "dustinkirkland/byobu"`, `binary_name == "byobu"`, and `requires == ["tmux"]`. +- [ ] Add `TestByobuInstallScript` assertions that `scripts/install_byobu.sh` exists, is executable, filters tags with the stable numeric expression `^[0-9]+([.][0-9]+)+$`, uses the GitHub tag archive URL, configures `--prefix="$INSTALL_PREFIX"`, supports uninstall, and verifies `$INSTALL_PREFIX/bin/byobu`. +- [ ] Add `TestTrustmuxCatalog` assertions for `catalog/trustmux.json`, `install_method == "uv_tool"`, `package_name == "trustmux"`, `binary_name == "trustmux"`, `source_kind == "pypi"`, and `requires == ["tmux"]`. +- [ ] Run `UV_CACHE_DIR=/tmp/cli-toolset-uv-cache /home/sme/.local/bin/uv run pytest tests/test_catalog_and_collectors.py -k 'Byobu or Trustmux' -q` and confirm failure because the new files do not exist. + +## Task 2: Implement the Byobu source installer and both catalog entries + +**Files:** +- Create: `scripts/install_byobu.sh` +- Create: `catalog/byobu.json` +- Create: `catalog/trustmux.json` +- Modify: `catalog/README.md` + +- [ ] Create `catalog/byobu.json` with category `general`, dedicated installer metadata, homepage `https://byobu.org/`, GitHub repository `dustinkirkland/byobu`, `byobu --version` detection, tmux dependency, and notes that source installation avoids stale apt packages. +- [ ] Create `catalog/trustmux.json` with category `security`, `uv_tool`, official homepage `https://trustmux.dev/`, PyPI package/binary `trustmux`, `trustmux --version` detection, tmux dependency, and notes that PyPI avoids stale distro packages. +- [ ] Create strict-mode `scripts/install_byobu.sh` with `install`, `update`, and `uninstall` dispatch. +- [ ] Implement latest-version lookup using the GitHub tags API, accepting only numeric stable tags and sorting them with `sort -V`; accept an explicit version as the second script argument for reproducible installs. +- [ ] Download `https://github.com/dustinkirkland/byobu/archive/refs/tags/${version}.tar.gz` with HTTPS failure handling, retries, timeouts, temporary-directory cleanup, and retained build-log tail on failure. +- [ ] Check required build commands, run `./autogen.sh`, `./configure --prefix="$INSTALL_PREFIX"`, `make`, and `make install`, then verify the direct path `$INSTALL_PREFIX/bin/byobu` reports the requested version. +- [ ] Make `scripts/install_byobu.sh` executable and update both catalog count references from 101 to 103. +- [ ] Run the focused tests and confirm they pass. +- [ ] Run `bash -n scripts/install_byobu.sh` and `shellcheck -x scripts/install_byobu.sh`. + +## Task 3: Record upstream versions and perform real user-local installs + +**Files:** +- Modify: `upstream_versions.json` +- Runtime outputs: `local_state.json` and audit snapshot (gitignored) +- Install destinations: `~/.local/bin/byobu` and the uv tool directory exposing `~/.local/bin/trustmux` + +- [ ] Update the committed baseline with `UV_CACHE_DIR=/tmp/cli-toolset-uv-cache /home/sme/.local/bin/uv run python audit.py --update-baseline byobu` and the equivalent command for `trustmux`. +- [ ] Confirm baseline versions are Byobu `7.15` from the GitHub tag collector and Trustmux `7.15` from PyPI, preserving unrelated baseline metadata. +- [ ] Install Byobu with `FORCE_INSTALL=1 ./scripts/install_byobu.sh update 7.15`. +- [ ] Install Trustmux with `UV_CACHE_DIR=/tmp/cli-toolset-uv-cache ./scripts/install_tool.sh trustmux update`. +- [ ] Verify `/home/sme/.local/bin/byobu --version` and `/home/sme/.local/bin/trustmux --version`. +- [ ] Refresh/merge local collection with `CLI_AUDIT_COLLECT=1 CLI_AUDIT_MERGE=1 UV_CACHE_DIR=/tmp/cli-toolset-uv-cache /home/sme/.local/bin/uv run python audit.py byobu trustmux`. +- [ ] Confirm JSON audit output reports both tools installed from user-local paths and up to date. + +## Task 4: Run complete verification and commit only scoped changes + +**Files:** +- Verify all changed files + +- [ ] Run `UV_CACHE_DIR=/tmp/cli-toolset-uv-cache /home/sme/.local/bin/uv run pytest -q`. +- [ ] Run `UV_CACHE_DIR=/tmp/cli-toolset-uv-cache /home/sme/.local/bin/uv run python -m flake8 cli_audit tests`. +- [ ] Run `./scripts/test_smoke.sh`. +- [ ] Run `git diff --check` and inspect `git status --short`. +- [ ] Preserve unrelated modifications to `scripts/lib/bashrc.sh`, `scripts/lib/completion.sh`, and `requirements-frozen-20260722.txt`. +- [ ] Commit the tmux installer fix separately as `fix(scripts): install tmux binary without manpage`. +- [ ] Commit the catalog/installer work, including Bubblewrap, Byobu, Trustmux, tests, baseline, and catalog counts, as `feat(catalog): add sandbox and terminal tools`. From 4d867b82b4a485ecf66c8134193ac760eed5457e Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 27 Jul 2026 14:31:47 +0200 Subject: [PATCH 3/5] fix(scripts): install tmux binary without manpage Signed-off-by: Sebastian Mendel --- scripts/install_tmux.sh | 96 +++++++++++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 18 deletions(-) diff --git a/scripts/install_tmux.sh b/scripts/install_tmux.sh index f325f70..8f7100c 100755 --- a/scripts/install_tmux.sh +++ b/scripts/install_tmux.sh @@ -4,11 +4,31 @@ set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib/install_strategy.sh . "$DIR/lib/install_strategy.sh" TOOL="tmux" INSTALL_PREFIX="${HOME}/.local" GITHUB_REPO="tmux/tmux" +BUILD_LOG="" +BUILD_TMPDIR="" + +cleanup() { + if [ -n "$BUILD_TMPDIR" ] && [ -d "$BUILD_TMPDIR" ]; then + rm -rf "$BUILD_TMPDIR" + fi +} +trap cleanup EXIT +trap 'exit 130' INT TERM + +show_build_failure() { + local step="$1" + echo "[$TOOL] Error: $step failed" >&2 + if [ -n "$BUILD_LOG" ] && [ -f "$BUILD_LOG" ]; then + echo "[$TOOL] Last build output:" >&2 + tail -n 40 "$BUILD_LOG" >&2 + fi +} get_installed_version() { if command -v tmux >/dev/null 2>&1; then @@ -16,12 +36,24 @@ get_installed_version() { fi } +get_target_version() { + local version="" + if command -v gh >/dev/null 2>&1; then + version="$(gh api "repos/$GITHUB_REPO/releases/latest" --jq '.tag_name' 2>/dev/null || true)" + fi + if [ -z "$version" ]; then + version="$(curl -fsSIL -H "User-Agent: cli-audit" -o /dev/null -w '%{url_effective}' \ + "https://github.com/$GITHUB_REPO/releases/latest" 2>/dev/null | awk -F/ '{print $NF}')" + fi + printf '%s' "$version" +} + install_tmux() { local version="${1:-}" if [ -z "$version" ]; then echo "[$TOOL] Fetching latest release..." >&2 - version="$(gh api repos/$GITHUB_REPO/releases/latest --jq '.tag_name' 2>/dev/null || true)" + version="$(get_target_version)" fi if [ -z "$version" ]; then @@ -29,10 +61,12 @@ install_tmux() { return 1 fi - local before="$(get_installed_version)" + local before + before="$(get_installed_version)" # Ensure build dependencies local missing_deps=() + dpkg -s build-essential >/dev/null 2>&1 || missing_deps+=(build-essential) dpkg -s libevent-dev >/dev/null 2>&1 || missing_deps+=(libevent-dev) dpkg -s libncurses-dev >/dev/null 2>&1 || missing_deps+=(libncurses-dev) dpkg -s bison >/dev/null 2>&1 || missing_deps+=(bison) @@ -48,24 +82,27 @@ install_tmux() { local tarball="tmux-${version}.tar.gz" local url="https://github.com/$GITHUB_REPO/releases/download/${version}/${tarball}" - local tmpdir="$(mktemp -d)" + BUILD_TMPDIR="$(mktemp -d)" + BUILD_LOG="$BUILD_TMPDIR/build.log" echo "[$TOOL] Downloading $url..." >&2 - if ! curl -sL "$url" -o "$tmpdir/$tarball"; then + if ! curl -fL --retry 3 --retry-delay 1 --connect-timeout 10 \ + "$url" -o "$BUILD_TMPDIR/$tarball"; then echo "[$TOOL] Error: Failed to download $url" >&2 - rm -rf "$tmpdir" return 1 fi echo "[$TOOL] Extracting and building..." >&2 - tar -xzf "$tmpdir/$tarball" -C "$tmpdir" + if ! tar -xzf "$BUILD_TMPDIR/$tarball" -C "$BUILD_TMPDIR"; then + echo "[$TOOL] Error: Invalid source archive: $url" >&2 + return 1 + fi # Find the extracted directory (may be tmux-3.6a or tmux-3.6) local src_dir - src_dir="$(find "$tmpdir" -maxdepth 1 -type d -name 'tmux-*' | head -1)" + src_dir="$(find "$BUILD_TMPDIR" -maxdepth 1 -type d -name 'tmux-*' | head -1)" if [ -z "$src_dir" ]; then echo "[$TOOL] Error: Could not find source directory in tarball" >&2 - rm -rf "$tmpdir" return 1 fi @@ -73,26 +110,49 @@ install_tmux() { # Configure and build if [ -f configure.ac ] && [ ! -f configure ]; then - autoreconf -fi + if ! autoreconf -fi >"$BUILD_LOG" 2>&1; then + show_build_failure "autoreconf" + return 1 + fi fi - ./configure --prefix="$INSTALL_PREFIX" >/dev/null 2>&1 + if ! ./configure --prefix="$INSTALL_PREFIX" >"$BUILD_LOG" 2>&1; then + show_build_failure "configure" + return 1 + fi - make -j"$(nproc)" >/dev/null 2>&1 + if ! make -j"$(nproc)" >>"$BUILD_LOG" 2>&1; then + show_build_failure "build" + return 1 + fi + # Install only the executable. `make install` also writes the optional + # manpage below ~/.local/share and can fail in restricted/sandboxed WSL + # environments after the binary has already been installed successfully. mkdir -p "$INSTALL_PREFIX/bin" - make install >/dev/null 2>&1 + if ! install -m 0755 tmux "$INSTALL_PREFIX/bin/tmux" >>"$BUILD_LOG" 2>&1; then + show_build_failure "install" + return 1 + fi cd / - rm -rf "$tmpdir" - - # Rehash so the new binary is found - hash -r 2>/dev/null || true + local installed_binary="$INSTALL_PREFIX/bin/tmux" + local after="" + if [ -x "$installed_binary" ]; then + after="$("$installed_binary" -V 2>/dev/null | grep -oE '[0-9]+\.[0-9]+[a-z]?' || true)" + fi + if [ "$after" != "$version" ]; then + echo "[$TOOL] Error: Installed binary reports '${after:-}', expected '$version'" >&2 + return 1 + fi - local after="$(get_installed_version)" printf "[%s] before: %s\n" "$TOOL" "${before:-}" printf "[%s] after: %s\n" "$TOOL" "${after:-}" - printf "[%s] path: %s\n" "$TOOL" "$(command -v tmux 2>/dev/null || echo "$INSTALL_PREFIX/bin/tmux")" + printf "[%s] path: %s\n" "$TOOL" "$installed_binary" + + if [ -n "${TMUX:-}" ]; then + echo "[$TOOL] note: tmux $after is installed; restart the running tmux server to use it" >&2 + fi # Refresh snapshot refresh_snapshot "$TOOL" From 732b5e2654504ed4969715cebcd158d3fbbd9dc9 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 27 Jul 2026 14:32:38 +0200 Subject: [PATCH 4/5] feat(catalog): add sandbox and terminal tools Signed-off-by: Sebastian Mendel --- catalog/README.md | 4 +- catalog/bwrap.json | 21 +++ catalog/byobu.json | 15 +++ catalog/trustmux.json | 15 +++ scripts/install_byobu.sh | 193 +++++++++++++++++++++++++++ tests/test_catalog_and_collectors.py | 82 +++++++++++- upstream_versions.json | 23 +++- 7 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 catalog/bwrap.json create mode 100644 catalog/byobu.json create mode 100644 catalog/trustmux.json create mode 100755 scripts/install_byobu.sh diff --git a/catalog/README.md b/catalog/README.md index 41ad057..455c093 100644 --- a/catalog/README.md +++ b/catalog/README.md @@ -71,7 +71,7 @@ Install tools via system package managers (apt, brew, dnf, pacman). 2. The tool will automatically be available via `make install-`, `make upgrade-`, `make uninstall-`, and `make reconcile-` 3. No need to create a custom install script! -Currently **100 tools** are cataloged. +Currently **103 tools** are cataloged. ## Environment Variables @@ -96,6 +96,6 @@ make upgrade ## Architecture -All 100 tools have catalog entries. The generic installer (`scripts/install_tool.sh`) reads a tool's catalog JSON and delegates to the appropriate method-specific installer under `scripts/installers/`. Tools with complex installation needs (python, node, docker, rust, etc.) use `install_method: "dedicated_script"` to route to their existing bespoke scripts. +All 103 tools have catalog entries. The generic installer (`scripts/install_tool.sh`) reads a tool's catalog JSON and delegates to the appropriate method-specific installer under `scripts/installers/`. Tools with complex installation needs (python, node, docker, rust, etc.) use `install_method: "dedicated_script"` to route to their existing bespoke scripts. See [ADR-007](../docs/adr/ADR-007-generic-tool-installation-architecture.md) for the full architectural decision record. diff --git a/catalog/bwrap.json b/catalog/bwrap.json new file mode 100644 index 0000000..f1aec3c --- /dev/null +++ b/catalog/bwrap.json @@ -0,0 +1,21 @@ +{ + "name": "bwrap", + "category": "security", + "install_method": "package_manager", + "description": "Unprivileged sandboxing tool based on Linux namespaces", + "homepage": "https://github.com/containers/bubblewrap", + "github_repo": "containers/bubblewrap", + "binary_name": "bwrap", + "version_command": "bwrap --version 2>/dev/null | head -1", + "packages": { + "apt": "bubblewrap", + "dnf": "bubblewrap", + "pacman": "bubblewrap" + }, + "tags": [ + "sandbox", + "security", + "linux" + ], + "notes": "Linux-only sandbox runtime. The package is named bubblewrap; the executable is bwrap." +} diff --git a/catalog/byobu.json b/catalog/byobu.json new file mode 100644 index 0000000..8051fa7 --- /dev/null +++ b/catalog/byobu.json @@ -0,0 +1,15 @@ +{ + "name": "byobu", + "category": "general", + "install_method": "dedicated_script", + "description": "Text-based window manager and terminal multiplexer enhancement", + "homepage": "https://byobu.org/", + "github_repo": "dustinkirkland/byobu", + "binary_name": "byobu", + "script": "install_byobu.sh", + "version_command": "awk -F= '$1 == \"VERSION\" { print $2; exit }' \"$(command -v byobu)\"", + "requires": [ + "tmux" + ], + "notes": "Built from the newest stable upstream source tag because distribution packages lag behind." +} diff --git a/catalog/trustmux.json b/catalog/trustmux.json new file mode 100644 index 0000000..c4f6d75 --- /dev/null +++ b/catalog/trustmux.json @@ -0,0 +1,15 @@ +{ + "name": "trustmux", + "category": "security", + "install_method": "uv_tool", + "description": "Secure mobile companion for remote tmux sessions over Tailscale", + "homepage": "https://trustmux.dev/", + "package_name": "trustmux", + "binary_name": "trustmux", + "source_kind": "pypi", + "version_command": "trustmuxd --version 2>/dev/null | head -1", + "requires": [ + "tmux" + ], + "notes": "Installed from the newest stable PyPI release instead of the distribution package bundled with Byobu." +} diff --git a/scripts/install_byobu.sh b/scripts/install_byobu.sh new file mode 100755 index 0000000..ec6043e --- /dev/null +++ b/scripts/install_byobu.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# Install the newest stable Byobu tag from upstream source. +# Distribution packages commonly lag behind the upstream release. +set -euo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib/install_strategy.sh +. "$DIR/lib/install_strategy.sh" + +TOOL="byobu" +INSTALL_PREFIX="${INSTALL_PREFIX:-${HOME}/.local}" +GITHUB_REPO="dustinkirkland/byobu" +MANIFEST_DIR="$INSTALL_PREFIX/share/cli-audit/manifests" +MANIFEST_FILE="$MANIFEST_DIR/byobu.txt" +BUILD_LOG="" +BUILD_TMPDIR="" + +cleanup() { + if [ -n "$BUILD_TMPDIR" ] && [ -d "$BUILD_TMPDIR" ]; then + rm -rf "$BUILD_TMPDIR" + fi +} +trap cleanup EXIT +trap 'exit 130' INT TERM + +show_build_failure() { + local step="$1" + echo "[$TOOL] Error: $step failed" >&2 + if [ -n "$BUILD_LOG" ] && [ -f "$BUILD_LOG" ]; then + echo "[$TOOL] Last build output:" >&2 + tail -n 40 "$BUILD_LOG" >&2 + fi +} + +require_commands() { + local command_name + local missing=() + + for command_name in curl jq tar sort make autoreconf automake autoconf; do + command -v "$command_name" >/dev/null 2>&1 || missing+=("$command_name") + done + + if [ "${#missing[@]}" -gt 0 ]; then + echo "[$TOOL] Error: Missing build commands: ${missing[*]}" >&2 + return 1 + fi +} + +get_installed_version() { + local installed_binary="$INSTALL_PREFIX/bin/byobu" + if [ -x "$installed_binary" ]; then + awk -F= '$1 == "VERSION" { print $2; exit }' "$installed_binary" + fi +} + +get_target_version() { + local tags_json="" + local version="" + + if command -v gh >/dev/null 2>&1; then + tags_json="$(gh api "repos/$GITHUB_REPO/tags?per_page=100" 2>/dev/null || true)" + fi + if [ -z "$tags_json" ]; then + tags_json="$(curl -fsSL --retry 3 --retry-delay 1 --connect-timeout 10 \ + -H "Accept: application/vnd.github+json" \ + -H "User-Agent: cli-audit" \ + "https://api.github.com/repos/$GITHUB_REPO/tags?per_page=100")" + fi + + version="$(printf '%s' "$tags_json" | + jq -r '.[].name' | + grep -E '^[0-9]+([.][0-9]+)+$' | + sort -V | + tail -1)" + printf '%s' "$version" +} + +remove_manifest_files() { + local relative_path + + if [ ! -f "$MANIFEST_FILE" ]; then + return 0 + fi + + while IFS= read -r relative_path; do + if [ -n "$relative_path" ]; then + rm -f "$INSTALL_PREFIX/$relative_path" + fi + done <"$MANIFEST_FILE" + rm -f "$MANIFEST_FILE" +} + +install_byobu() { + local version="${1:-}" + local before="" + local after="" + local archive="" + local url="" + local src_dir="" + local stage_dir="" + local installed_binary="$INSTALL_PREFIX/bin/byobu" + + require_commands + + if [ -z "$version" ]; then + echo "[$TOOL] Fetching latest stable tag..." >&2 + version="$(get_target_version)" + fi + if ! grep -Eq '^[0-9]+([.][0-9]+)+$' <<<"$version"; then + echo "[$TOOL] Error: Invalid stable version: ${version:-}" >&2 + return 1 + fi + + before="$(get_installed_version)" + BUILD_TMPDIR="$(mktemp -d)" + BUILD_LOG="$BUILD_TMPDIR/build.log" + archive="$BUILD_TMPDIR/byobu-$version.tar.gz" + stage_dir="$BUILD_TMPDIR/stage" + url="https://github.com/$GITHUB_REPO/archive/refs/tags/${version}.tar.gz" + + echo "[$TOOL] Downloading $url..." >&2 + if ! curl -fL --retry 3 --retry-delay 1 --connect-timeout 10 \ + "$url" -o "$archive"; then + echo "[$TOOL] Error: Failed to download $url" >&2 + return 1 + fi + if ! tar -xzf "$archive" -C "$BUILD_TMPDIR"; then + echo "[$TOOL] Error: Invalid source archive: $url" >&2 + return 1 + fi + + src_dir="$(find "$BUILD_TMPDIR" -maxdepth 1 -type d -name 'byobu-*' | head -1)" + if [ -z "$src_dir" ]; then + echo "[$TOOL] Error: Could not find source directory in archive" >&2 + return 1 + fi + + cd "$src_dir" + if ! ./autogen.sh >"$BUILD_LOG" 2>&1; then + show_build_failure "autogen" + return 1 + fi + if ! ./configure --prefix="$INSTALL_PREFIX" --disable-trustmux >>"$BUILD_LOG" 2>&1; then + show_build_failure "configure" + return 1 + fi + if ! make -j"$(nproc)" >>"$BUILD_LOG" 2>&1; then + show_build_failure "build" + return 1 + fi + if ! make install DESTDIR="$stage_dir" >>"$BUILD_LOG" 2>&1; then + show_build_failure "staged install" + return 1 + fi + + mkdir -p "$INSTALL_PREFIX" "$MANIFEST_DIR" + remove_manifest_files + if ! cp -a "$stage_dir$INSTALL_PREFIX/." "$INSTALL_PREFIX/" >>"$BUILD_LOG" 2>&1; then + show_build_failure "install" + return 1 + fi + find "$stage_dir$INSTALL_PREFIX" \( -type f -o -type l \) -printf '%P\n' | + sort >"$MANIFEST_FILE" + + cd / + if [ -x "$installed_binary" ]; then + after="$(get_installed_version)" + fi + if [ "$after" != "$version" ]; then + echo "[$TOOL] Error: Installed binary reports '${after:-}', expected '$version'" >&2 + return 1 + fi + + printf "[%s] before: %s\n" "$TOOL" "${before:-}" + printf "[%s] after: %s\n" "$TOOL" "$after" + printf "[%s] path: %s\n" "$TOOL" "$installed_binary" + refresh_snapshot "$TOOL" +} + +case "${1:-install}" in + install|update) + install_byobu "${2:-}" + ;; + uninstall) + echo "[$TOOL] Removing user-local installation" >&2 + remove_manifest_files + hash -r 2>/dev/null || true + ;; + *) + echo "Usage: $0 [install|update|uninstall] [version]" >&2 + exit 1 + ;; +esac diff --git a/tests/test_catalog_and_collectors.py b/tests/test_catalog_and_collectors.py index 7c000ca..4eb2208 100644 --- a/tests/test_catalog_and_collectors.py +++ b/tests/test_catalog_and_collectors.py @@ -10,10 +10,10 @@ import json import os -import pytest from pathlib import Path -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch +import pytest # Get the project root directory PROJECT_ROOT = Path(__file__).parent.parent @@ -217,8 +217,7 @@ def test_install_composer_checks_php(self): # Should check for PHP assert "command -v php" in content, "Script should check for PHP" - assert "Error: PHP is required" in content or "PHP is required" in content, \ - "Script should mention PHP requirement" + assert "Error: PHP is required" in content or "PHP is required" in content, "Script should mention PHP requirement" class TestGitHubRateLimitHelpers: @@ -227,6 +226,7 @@ class TestGitHubRateLimitHelpers: def test_get_github_rate_limit_help_exists(self): """Test that get_github_rate_limit_help function exists.""" from cli_audit.collectors import get_github_rate_limit_help + assert callable(get_github_rate_limit_help) def test_get_github_rate_limit_help_content(self): @@ -247,6 +247,7 @@ def test_get_github_rate_limit_help_content(self): def test_get_gh_cli_token_exists(self): """Test that get_gh_cli_token function exists.""" from cli_audit.collectors import get_gh_cli_token + assert callable(get_gh_cli_token) @patch("shutil.which") @@ -308,8 +309,8 @@ class TestGitHubRateLimitDisplay: def test_audit_imports_help_function(self): """Test that audit.py imports get_github_rate_limit_help.""" import audit - assert hasattr(audit, 'get_github_rate_limit_help') or \ - 'get_github_rate_limit_help' in dir(audit) + + assert hasattr(audit, "get_github_rate_limit_help") or "get_github_rate_limit_help" in dir(audit) class TestCatalogIntegrity: @@ -344,3 +345,72 @@ def test_all_catalog_files_have_install_method(self): with open(json_file) as f: data = json.load(f) assert "install_method" in data, f"{json_file.name} should have 'install_method' field" + + +class TestBubblewrapCatalog: + """Tests for the Bubblewrap catalog entry.""" + + def test_bwrap_catalog_uses_distribution_package_name(self): + catalog_path = PROJECT_ROOT / "catalog" / "bwrap.json" + + with open(catalog_path) as f: + data = json.load(f) + + assert data["name"] == "bwrap" + assert data["binary_name"] == "bwrap" + assert data["install_method"] == "package_manager" + assert data["packages"]["apt"] == "bubblewrap" + assert data["github_repo"] == "containers/bubblewrap" + + +class TestByobuCatalog: + """Tests for the upstream Byobu catalog entry.""" + + def test_byobu_catalog_uses_dedicated_source_installer(self): + catalog_path = PROJECT_ROOT / "catalog" / "byobu.json" + + with open(catalog_path) as f: + data = json.load(f) + + assert data["name"] == "byobu" + assert data["install_method"] == "dedicated_script" + assert data["script"] == "install_byobu.sh" + assert data["github_repo"] == "dustinkirkland/byobu" + assert data["binary_name"] == "byobu" + assert data["requires"] == ["tmux"] + assert '"VERSION"' in data["version_command"] + + +class TestByobuInstallScript: + """Tests for the dedicated upstream Byobu installer.""" + + def test_byobu_installer_is_executable_and_filters_stable_tags(self): + script_path = PROJECT_ROOT / "scripts" / "install_byobu.sh" + + assert script_path.exists() + assert os.access(script_path, os.X_OK) + + content = script_path.read_text() + assert "^[0-9]+([.][0-9]+)+$" in content + assert "archive/refs/tags/${version}.tar.gz" in content + assert './configure --prefix="$INSTALL_PREFIX"' in content + assert "$INSTALL_PREFIX/bin/byobu" in content + assert "uninstall)" in content + + +class TestTrustmuxCatalog: + """Tests for the upstream Trustmux PyPI catalog entry.""" + + def test_trustmux_catalog_uses_uv_tool(self): + catalog_path = PROJECT_ROOT / "catalog" / "trustmux.json" + + with open(catalog_path) as f: + data = json.load(f) + + assert data["name"] == "trustmux" + assert data["install_method"] == "uv_tool" + assert data["package_name"] == "trustmux" + assert data["binary_name"] == "trustmux" + assert data["source_kind"] == "pypi" + assert data["requires"] == ["tmux"] + assert data["version_command"].startswith("trustmuxd --version") diff --git a/upstream_versions.json b/upstream_versions.json index f4ea065..be9e814 100644 --- a/upstream_versions.json +++ b/upstream_versions.json @@ -1,6 +1,6 @@ { "__meta__": { - "baseline_updated_at": "2025-12-21T12:41:48Z", + "baseline_updated_at": "2026-07-27T12:27:44Z", "schema_version": 2, "source": "github/pypi/npm/crates API" }, @@ -47,6 +47,20 @@ "tool_url": "https://pypi.org/project/black/", "upstream_method": "pypi" }, + "bwrap": { + "latest_tag": "v0.11.2", + "latest_url": "https://github.com/containers/bubblewrap/releases/tag/v0.11.2", + "latest_version": "0.11.2", + "tool_url": "https://github.com/containers/bubblewrap", + "upstream_method": "gh" + }, + "byobu": { + "latest_tag": "7.15", + "latest_url": "https://github.com/dustinkirkland/byobu/releases/tag/7.15", + "latest_version": "7.15", + "tool_url": "https://github.com/dustinkirkland/byobu", + "upstream_method": "gh" + }, "claude": { "latest_tag": "2.0.76", "latest_url": "https://www.npmjs.com/package/@anthropic-ai/claude-code", @@ -512,6 +526,13 @@ "tool_url": "https://github.com/aquasecurity/trivy", "upstream_method": "gh" }, + "trustmux": { + "latest_tag": "7.15", + "latest_url": "https://pypi.org/project/trustmux/", + "latest_version": "7.15", + "tool_url": "https://pypi.org/project/trustmux/", + "upstream_method": "pypi" + }, "uv": { "latest_tag": "0.9.18", "latest_url": "https://github.com/astral-sh/uv/releases/tag/0.9.18", From 3bfff07dc9b6ef54d2cd37b11103083f899ca3ea Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 27 Jul 2026 14:44:22 +0200 Subject: [PATCH 5/5] fix(scripts): enforce HTTPS for source downloads Signed-off-by: Sebastian Mendel --- scripts/install_byobu.sh | 6 ++++-- scripts/install_tmux.sh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/install_byobu.sh b/scripts/install_byobu.sh index ec6043e..2359399 100755 --- a/scripts/install_byobu.sh +++ b/scripts/install_byobu.sh @@ -61,7 +61,8 @@ get_target_version() { tags_json="$(gh api "repos/$GITHUB_REPO/tags?per_page=100" 2>/dev/null || true)" fi if [ -z "$tags_json" ]; then - tags_json="$(curl -fsSL --retry 3 --retry-delay 1 --connect-timeout 10 \ + tags_json="$(curl --proto '=https' --proto-redir '=https' -fsSL \ + --retry 3 --retry-delay 1 --connect-timeout 10 \ -H "Accept: application/vnd.github+json" \ -H "User-Agent: cli-audit" \ "https://api.github.com/repos/$GITHUB_REPO/tags?per_page=100")" @@ -119,7 +120,8 @@ install_byobu() { url="https://github.com/$GITHUB_REPO/archive/refs/tags/${version}.tar.gz" echo "[$TOOL] Downloading $url..." >&2 - if ! curl -fL --retry 3 --retry-delay 1 --connect-timeout 10 \ + if ! curl --proto '=https' --proto-redir '=https' -fL \ + --retry 3 --retry-delay 1 --connect-timeout 10 \ "$url" -o "$archive"; then echo "[$TOOL] Error: Failed to download $url" >&2 return 1 diff --git a/scripts/install_tmux.sh b/scripts/install_tmux.sh index 8f7100c..13c5b8a 100755 --- a/scripts/install_tmux.sh +++ b/scripts/install_tmux.sh @@ -42,7 +42,8 @@ get_target_version() { version="$(gh api "repos/$GITHUB_REPO/releases/latest" --jq '.tag_name' 2>/dev/null || true)" fi if [ -z "$version" ]; then - version="$(curl -fsSIL -H "User-Agent: cli-audit" -o /dev/null -w '%{url_effective}' \ + version="$(curl --proto '=https' --proto-redir '=https' -fsSIL \ + -H "User-Agent: cli-audit" -o /dev/null -w '%{url_effective}' \ "https://github.com/$GITHUB_REPO/releases/latest" 2>/dev/null | awk -F/ '{print $NF}')" fi printf '%s' "$version" @@ -86,7 +87,8 @@ install_tmux() { BUILD_LOG="$BUILD_TMPDIR/build.log" echo "[$TOOL] Downloading $url..." >&2 - if ! curl -fL --retry 3 --retry-delay 1 --connect-timeout 10 \ + if ! curl --proto '=https' --proto-redir '=https' -fL \ + --retry 3 --retry-delay 1 --connect-timeout 10 \ "$url" -o "$BUILD_TMPDIR/$tarball"; then echo "[$TOOL] Error: Failed to download $url" >&2 return 1