From e658d6d375d14545d954cc67c187a78307f306cc Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sat, 20 Jun 2026 19:05:20 +0530 Subject: [PATCH] feat: Dynamic package registry --- .agents/skills/add_installer/SKILL.md | 25 +++++------ VERSION | 2 +- b.sh | 43 ++++++++++++------- bootstrap.sh | 1 + commands/help.sh | 5 +-- installers/install_agy.sh | 3 ++ installers/install_bat.sh | 3 ++ installers/install_node.sh | 3 ++ installers/install_nvim.sh | 3 ++ installers/install_pnpm.sh | 3 ++ installers/install_rust.sh | 3 ++ installers/install_starship.sh | 3 ++ installers/install_yay.sh | 3 ++ installers/install_yazi.sh | 3 ++ installers/install_zoxide.sh | 3 ++ readme.md | 6 +++ registry.sh | 29 +++++++++++++ routes.sh | 60 ++++++++++++++++++--------- scripts/generate_registry.sh | 56 +++++++++++++++++++++++++ scripts/pre-commit | 6 +++ 20 files changed, 210 insertions(+), 53 deletions(-) create mode 100644 registry.sh create mode 100755 scripts/generate_registry.sh diff --git a/.agents/skills/add_installer/SKILL.md b/.agents/skills/add_installer/SKILL.md index 78f99fe..fda6442 100644 --- a/.agents/skills/add_installer/SKILL.md +++ b/.agents/skills/add_installer/SKILL.md @@ -35,22 +35,16 @@ When adding a new installer named ``: Create `installers/install_.sh` using the template below. -### Step 2: Register in `routes.sh` +### Step 2: Add metadata comments to the top of your installer script -Make **two** edits to `routes.sh`: +At the top of your new installer script, right below `#!/usr/bin/env bash`, add the following three metadata headers: +```bash +# Tool: +# DisplayName: +# Description: +``` -1. **Add to the `INSTALLERS` associative array** (line ~19-26). Insert a new entry in alphabetical order: - ```bash - []="Short description of what it installs" - ``` - -2. **Add to the `INSTALLER_KEYS` array** (line ~28). Insert the key in alphabetical order: - ```bash - INSTALLER_KEYS=(agy bat node nvim yazi zoxide) - ``` - -> [!IMPORTANT] -> Both arrays must be kept in sync and in alphabetical order. +The central router `routes.sh` and autocomplete function in `b.sh` will dynamically parse this metadata from all `install_*.sh` scripts to register the installer and keys automatically! No manual edits to `routes.sh` or `b.sh` are required. ### Step 3: Verify (optional) @@ -67,6 +61,9 @@ Every installer follows this exact boilerplate structure. Copy this and fill in ```bash #!/usr/bin/env bash +# Tool: +# DisplayName: +# Description: Short description of what it installs # # Installer Script # diff --git a/VERSION b/VERSION index 524cb55..45a1b3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.1 +1.1.2 diff --git a/b.sh b/b.sh index 9d9b7d4..44b749e 100755 --- a/b.sh +++ b/b.sh @@ -77,16 +77,23 @@ _b_completion() { # If completing the first argument after 'b' if [ "$COMP_CWORD" -eq 1 ]; then - opts="all con bye up ware" + opts="all con bye up ware bware" - local routes_file="$HOME/.config/bootstrap/routes.sh" - if [ -f "$routes_file" ]; then - # Extract installer keys dynamically from the routes.sh file - local installer_keys - installer_keys=$(grep -E "^INSTALLER_KEYS=" "$routes_file" 2>/dev/null | sed -E 's/INSTALLER_KEYS=\(([^)]+)\)/\1/' 2>/dev/null) - if [ -n "$installer_keys" ]; then - opts="$opts $installer_keys" - fi + local routes_dir="$HOME/.config/bootstrap" + local installer_keys="" + if [ -d "$routes_dir/installers" ]; then + for f in "$routes_dir/installers"/install_*.sh; do + if [ -f "$f" ]; then + local tool + tool=$(grep -E "^# Tool:" "$f" | head -n1 | sed -E 's/^# Tool:\s*//I') + if [ -n "$tool" ]; then + installer_keys="$installer_keys $tool" + fi + fi + done + fi + if [ -n "$installer_keys" ]; then + opts="$opts $installer_keys" fi # Support comma-separated completions (e.g. b nvim,ya) @@ -111,12 +118,20 @@ _b_completion() { return 0 fi - # If completing arguments for 'b ware ' - if [ "$COMP_CWORD" -eq 2 ] && [ "$prev" = "ware" ]; then - local routes_file="$HOME/.config/bootstrap/routes.sh" + # If completing arguments for 'b ware ' or 'b bware ' + if [ "$COMP_CWORD" -eq 2 ] && { [ "$prev" = "ware" ] || [ "$prev" = "bware" ]; }; then + local routes_dir="$HOME/.config/bootstrap" local installer_keys="" - if [ -f "$routes_file" ]; then - installer_keys=$(grep -E "^INSTALLER_KEYS=" "$routes_file" 2>/dev/null | sed -E 's/INSTALLER_KEYS=\(([^)]+)\)/\1/' 2>/dev/null) + if [ -d "$routes_dir/installers" ]; then + for f in "$routes_dir/installers"/install_*.sh; do + if [ -f "$f" ]; then + local tool + tool=$(grep -E "^# Tool:" "$f" | head -n1 | sed -E 's/^# Tool:\s*//I') + if [ -n "$tool" ]; then + installer_keys="$installer_keys $tool" + fi + fi + done fi [ -z "$installer_keys" ] && installer_keys="agy bat node nvim pnpm rust starship yay yazi zoxide" diff --git a/bootstrap.sh b/bootstrap.sh index 52f645f..16ad83d 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -90,6 +90,7 @@ install_bootstrap() { "VERSION" "b.sh" "routes.sh" + "registry.sh" "lib/common.sh" "lib/platform.sh" "lib/shell_config.sh" diff --git a/commands/help.sh b/commands/help.sh index 31277d4..4c13ce9 100644 --- a/commands/help.sh +++ b/commands/help.sh @@ -9,7 +9,4 @@ printf " %-6s - %s\n" "up" "Check for updates and update Bootstrap CLI" printf " %-6s - %s\n" "ware" "Edit and run an installer (e.g. b ware nvim)" printf " %-6s - %s\n" "bye" "Uninstall Bootstrap CLI helper" -# Installers second -for key in "${INSTALLER_KEYS[@]}"; do - printf " %-6s - %s\n" "$key" "${INSTALLERS[$key]}" -done + diff --git a/installers/install_agy.sh b/installers/install_agy.sh index 34dffd4..ddb74d4 100644 --- a/installers/install_agy.sh +++ b/installers/install_agy.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: agy +# DisplayName: Antigravity +# Description: Install Antigravity CLI # # Antigravity CLI Installer Script (Linux Only) # diff --git a/installers/install_bat.sh b/installers/install_bat.sh index 0108d77..e9c209c 100644 --- a/installers/install_bat.sh +++ b/installers/install_bat.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: bat +# DisplayName: Bat +# Description: Install Bat (alternative to cat) and configure alias # # Bat Installer Script # diff --git a/installers/install_node.sh b/installers/install_node.sh index 49ee60a..7d65b49 100644 --- a/installers/install_node.sh +++ b/installers/install_node.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: node +# DisplayName: Node +# Description: Install Node.js (LTS) and NVM # # Node.js and NVM Installer Script # diff --git a/installers/install_nvim.sh b/installers/install_nvim.sh index 2509721..0da2731 100644 --- a/installers/install_nvim.sh +++ b/installers/install_nvim.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: nvim +# DisplayName: Neovim +# Description: Install Neovim 0.11.7 and configuration # # Neovim Installer Script # diff --git a/installers/install_pnpm.sh b/installers/install_pnpm.sh index 172d49f..830da67 100644 --- a/installers/install_pnpm.sh +++ b/installers/install_pnpm.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: pnpm +# DisplayName: Pnpm +# Description: Install pnpm package manager # # pnpm Installer Script # diff --git a/installers/install_rust.sh b/installers/install_rust.sh index 604e296..d64816c 100644 --- a/installers/install_rust.sh +++ b/installers/install_rust.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: rust +# DisplayName: Rust +# Description: Install Rustup and Rust compiler/toolchain # # Rust Installer Script (Simplified Local Rustup Init) # diff --git a/installers/install_starship.sh b/installers/install_starship.sh index 6451505..07488ea 100644 --- a/installers/install_starship.sh +++ b/installers/install_starship.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: starship +# DisplayName: Starship +# Description: Install Starship shell prompt # # Starship Installer Script # diff --git a/installers/install_yay.sh b/installers/install_yay.sh index 03dbefc..af92d09 100755 --- a/installers/install_yay.sh +++ b/installers/install_yay.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: yay +# DisplayName: Yay +# Description: Install Yay AUR helper # # Yay Installer Script # diff --git a/installers/install_yazi.sh b/installers/install_yazi.sh index df28110..3ac66fb 100755 --- a/installers/install_yazi.sh +++ b/installers/install_yazi.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: yazi +# DisplayName: Yazi +# Description: Install Yazi terminal file manager and dependencies # # Yazi Installer Script # diff --git a/installers/install_zoxide.sh b/installers/install_zoxide.sh index 21c2505..4206492 100755 --- a/installers/install_zoxide.sh +++ b/installers/install_zoxide.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tool: zoxide +# DisplayName: Zoxide +# Description: Install Zoxide directory jumper # # Zoxide Installer Script # diff --git a/readme.md b/readme.md index 51fb063..e2bc4b4 100644 --- a/readme.md +++ b/readme.md @@ -58,6 +58,12 @@ To bypass the editor and install the tool directly using the `ware` command, app b ware nvim -y ``` +To list all available installer tools and their descriptions, run the `ware` (or `bware`) command without any arguments: + +```bash +b ware +``` + You can also edit configurations located in your `~/.config/` directory by running: ```bash diff --git a/registry.sh b/registry.sh new file mode 100644 index 0000000..50aec76 --- /dev/null +++ b/registry.sh @@ -0,0 +1,29 @@ +# This file is auto-generated by scripts/generate_registry.sh. Do not edit manually. + +declare -A INSTALLERS=( + [agy]="Install Antigravity CLI" + [bat]="Install Bat (alternative to cat) and configure alias" + [node]="Install Node.js (LTS) and NVM" + [nvim]="Install Neovim 0.11.7 and configuration" + [pnpm]="Install pnpm package manager" + [rust]="Install Rustup and Rust compiler/toolchain" + [starship]="Install Starship shell prompt" + [yay]="Install Yay AUR helper" + [yazi]="Install Yazi terminal file manager and dependencies" + [zoxide]="Install Zoxide directory jumper" +) + +declare -A INSTALLER_DISPLAYS=( + [agy]="Antigravity" + [bat]="Bat" + [node]="Node" + [nvim]="Neovim" + [pnpm]="Pnpm" + [rust]="Rust" + [starship]="Starship" + [yay]="Yay" + [yazi]="Yazi" + [zoxide]="Zoxide" +) + +INSTALLER_KEYS=(agy bat node nvim pnpm rust starship yay yazi zoxide) diff --git a/routes.sh b/routes.sh index 0919e7c..330b2cf 100755 --- a/routes.sh +++ b/routes.sh @@ -15,21 +15,35 @@ fi require_bash -# Registry of installers -declare -A INSTALLERS=( - [agy]="Install Antigravity CLI" - [bat]="Install Bat (alternative to cat) and configure alias" - [node]="Install Node.js (LTS) and NVM" - [nvim]="Install Neovim 0.11.7 and configuration" - [pnpm]="Install pnpm package manager" - [rust]="Install Rustup and Rust compiler/toolchain" - [starship]="Install Starship shell prompt" - [yay]="Install Yay AUR helper" - [yazi]="Install Yazi terminal file manager and dependencies" - [zoxide]="Install Zoxide directory jumper" -) -# Order in which installers should be displayed -INSTALLER_KEYS=(agy bat node nvim pnpm rust starship yay yazi zoxide) +_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null || pwd)" + +# Source registry +if [ -f "$_SCRIPT_DIR/registry.sh" ]; then + . "$_SCRIPT_DIR/registry.sh" +elif [ -f "$BOOTSTRAP_DIR/registry.sh" ]; then + . "$BOOTSTRAP_DIR/registry.sh" +else + # Standalone/remote fallback: download registry + _tmp_registry=$(mktemp) + BOOTSTRAP_BASE_URL="${BOOTSTRAP_BASE_URL:-https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master}" + BOOTSTRAP_FALLBACK_URL="${BOOTSTRAP_FALLBACK_URL:-https://raw.githubusercontent.com/sortedcord/bootstrap/refs/heads/master}" + if has_command curl; then + curl -fsSL "${BOOTSTRAP_BASE_URL}/registry.sh" -o "$_tmp_registry" 2>/dev/null || \ + curl -fsSL "${BOOTSTRAP_FALLBACK_URL}/registry.sh" -o "$_tmp_registry" 2>/dev/null + elif has_command wget; then + wget -qO "$_tmp_registry" "${BOOTSTRAP_BASE_URL}/registry.sh" 2>/dev/null || \ + wget -qO "$_tmp_registry" "${BOOTSTRAP_FALLBACK_URL}/registry.sh" 2>/dev/null + fi + if [ -s "$_tmp_registry" ]; then + . "$_tmp_registry" + else + # Critical fallback + declare -A INSTALLERS + declare -A INSTALLER_DISPLAYS + INSTALLER_KEYS=() + fi + rm -f "$_tmp_registry" +fi # Helper function to run/edit installer scripts run_ware() { @@ -47,8 +61,11 @@ run_ware() { fi done - # Capitalize first letter for display (e.g. nvim -> Neovim) - local display_name="$(echo "${tool:0:1}" | tr '[:lower:]' '[:upper:]')${tool:1}" + # Resolve display name from metadata or fallback + local display_name="${INSTALLER_DISPLAYS[$tool]:-}" + if [ -z "$display_name" ]; then + display_name="$(echo "${tool:0:1}" | tr '[:lower:]' '[:upper:]')${tool:1}" + fi # Check for local installer first local local_installer="$BOOTSTRAP_DIR/installers/install_${tool}.sh" @@ -166,11 +183,14 @@ for script in "${SCRIPTS[@]}"; do exit 1 fi ;; - ware) + ware|bware) tools_arg="${1:-}" if [ -z "$tools_arg" ]; then - log_error "Usage: b ware [-y]" - exit 1 + echo "Available tools:" + for key in "${INSTALLER_KEYS[@]}"; do + printf " %-10s - %s\n" "$key" "${INSTALLERS[$key]}" + done + exit 0 fi shift IFS=',' read -ra WARE_TOOLS <<< "$tools_arg" diff --git a/scripts/generate_registry.sh b/scripts/generate_registry.sh new file mode 100755 index 0000000..5dc8251 --- /dev/null +++ b/scripts/generate_registry.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Automatically generate registry.sh from installer headers. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(dirname "$SCRIPT_DIR")" +INSTALLERS_DIR="$REPO_DIR/installers" +REGISTRY_FILE="$REPO_DIR/registry.sh" + +echo "==> Generating registry.sh..." + +# Temporary arrays +declare -A tools_desc +declare -A tools_disp +keys=() + +for f in "$INSTALLERS_DIR"/install_*.sh; do + [ -f "$f" ] || continue + tool=$(grep -E "^# Tool:" "$f" | head -n1 | sed -E 's/^# Tool:\s*//I') + disp_name=$(grep -E "^# DisplayName:" "$f" | head -n1 | sed -E 's/^# DisplayName:\s*//I') + desc=$(grep -E "^# Description:" "$f" | head -n1 | sed -E 's/^# Description:\s*//I') + + if [ -n "$tool" ]; then + tools_desc["$tool"]="$desc" + tools_disp["$tool"]="${disp_name:-$tool}" + keys+=("$tool") + fi +done + +# Sort keys alphabetically +sorted_keys=($(printf '%s\n' "${keys[@]}" | sort)) + +{ + echo "# This file is auto-generated by scripts/generate_registry.sh. Do not edit manually." + echo "" + echo "declare -A INSTALLERS=(" + for k in "${sorted_keys[@]}"; do + # Escape any double quotes in description + escaped_desc=$(echo "${tools_desc[$k]}" | sed 's/"/\\"/g') + echo " [$k]=\"$escaped_desc\"" + done + echo ")" + echo "" + echo "declare -A INSTALLER_DISPLAYS=(" + for k in "${sorted_keys[@]}"; do + escaped_disp=$(echo "${tools_disp[$k]}" | sed 's/"/\\"/g') + echo " [$k]=\"$escaped_disp\"" + done + echo ")" + echo "" + # Format keys output as space-separated list in array declaration format + echo "INSTALLER_KEYS=(${sorted_keys[*]})" +} > "$REGISTRY_FILE" + +echo "==> registry.sh successfully generated with ${#sorted_keys[@]} tools." diff --git a/scripts/pre-commit b/scripts/pre-commit index e28c3fa..df4bde7 100644 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -4,6 +4,12 @@ set -euo pipefail +# Generate the registry dynamically and stage it +if [ -f "./scripts/generate_registry.sh" ]; then + ./scripts/generate_registry.sh + git add registry.sh +fi + VERSION_FILE="VERSION" if [ ! -f "$VERSION_FILE" ]; then