aidevops 2.105.2 → 2.105.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/VERSION +1 -1
  2. package/aidevops.sh +30 -14
  3. package/package.json +1 -1
  4. package/setup.sh +179 -62
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.105.2
1
+ 2.105.5
package/aidevops.sh CHANGED
@@ -3,7 +3,7 @@
3
3
  # AI DevOps Framework CLI
4
4
  # Usage: aidevops <command> [options]
5
5
  #
6
- # Version: 2.105.2
6
+ # Version: 2.105.5
7
7
 
8
8
  set -euo pipefail
9
9
 
@@ -25,6 +25,9 @@ REPOS_FILE="$CONFIG_DIR/repos.json"
25
25
  REPO_URL="https://github.com/marcusquinn/aidevops.git"
26
26
  VERSION_FILE="$INSTALL_DIR/VERSION"
27
27
 
28
+ # Portable sed in-place edit (macOS BSD sed vs GNU sed)
29
+ sed_inplace() { if [[ "$(uname)" == "Darwin" ]]; then sed -i '' "$@"; else sed -i "$@"; fi; }
30
+
28
31
  print_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
29
32
  print_success() { echo -e "${GREEN}[OK]${NC} $1"; }
30
33
  print_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; }
@@ -588,8 +591,24 @@ cmd_update() {
588
591
  fi
589
592
  else
590
593
  print_warning "Repository not found, performing fresh install..."
591
- # shellcheck disable=SC2312 # curl|bash is intentional for install
592
- bash <(curl -fsSL https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh)
594
+ # Download setup script to temp file first (not piped to shell)
595
+ local tmp_setup
596
+ tmp_setup=$(mktemp "${TMPDIR:-/tmp}/aidevops-setup-XXXXXX.sh") || {
597
+ print_error "Failed to create temp file for setup script"
598
+ return 1
599
+ }
600
+ if curl -fsSL "https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh" -o "$tmp_setup" 2>/dev/null && [[ -s "$tmp_setup" ]]; then
601
+ chmod +x "$tmp_setup"
602
+ bash "$tmp_setup"
603
+ local setup_exit=$?
604
+ rm -f "$tmp_setup"
605
+ [[ $setup_exit -ne 0 ]] && return 1
606
+ else
607
+ rm -f "$tmp_setup"
608
+ print_error "Failed to download setup script"
609
+ print_info "Try: git clone https://github.com/marcusquinn/aidevops.git $INSTALL_DIR && bash $INSTALL_DIR/setup.sh"
610
+ return 1
611
+ fi
593
612
  fi
594
613
 
595
614
  # Check registered repos for updates
@@ -761,8 +780,7 @@ cmd_uninstall() {
761
780
  # Create backup
762
781
  cp "$rc_file" "$rc_file.bak"
763
782
  # Remove our alias block (from comment to empty line)
764
- sed -i.tmp '/# AI Assistant Server Access Framework/,/^$/d' "$rc_file"
765
- rm -f "$rc_file.tmp"
783
+ sed_inplace '/# AI Assistant Server Access Framework/,/^$/d' "$rc_file"
766
784
  print_success "Removed aliases from $rc_file"
767
785
  fi
768
786
  fi
@@ -798,7 +816,8 @@ cmd_uninstall() {
798
816
  echo ""
799
817
  print_success "Uninstall complete!"
800
818
  print_info "To reinstall, run:"
801
- echo " bash <(curl -fsSL https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh)"
819
+ echo " npm install -g aidevops && aidevops update"
820
+ echo " OR: brew install marcusquinn/tap/aidevops && aidevops update"
802
821
  }
803
822
 
804
823
  # Init command - initialize aidevops in a project
@@ -1360,8 +1379,7 @@ cmd_upgrade_planning() {
1360
1379
  fi
1361
1380
 
1362
1381
  # Update date placeholder
1363
- sed -i.tmp "s/{{DATE}}/$(date +%Y-%m-%d)/" "$todo_file" 2>/dev/null || true
1364
- rm -f "${todo_file}.tmp"
1382
+ sed_inplace "s/{{DATE}}/$(date +%Y-%m-%d)/" "$todo_file" 2>/dev/null || true
1365
1383
 
1366
1384
  # Merge existing tasks into Backlog section (after the TOON block closing tag)
1367
1385
  if [[ -n "$existing_tasks" ]]; then
@@ -1426,8 +1444,7 @@ cmd_upgrade_planning() {
1426
1444
  fi
1427
1445
 
1428
1446
  # Update date placeholder
1429
- sed -i.tmp "s/{{DATE}}/$(date +%Y-%m-%d)/" "$plans_file" 2>/dev/null || true
1430
- rm -f "${plans_file}.tmp"
1447
+ sed_inplace "s/{{DATE}}/$(date +%Y-%m-%d)/" "$plans_file" 2>/dev/null || true
1431
1448
 
1432
1449
  # Merge existing plans into Active Plans section (after the TOON block closing tag)
1433
1450
  if [[ -n "$existing_plans" ]]; then
@@ -1483,8 +1500,7 @@ cmd_upgrade_planning() {
1483
1500
  ' "$config_file" > "$temp_json" && mv "$temp_json" "$config_file"
1484
1501
  else
1485
1502
  # Update existing templates_version
1486
- sed -i.tmp "s/\"templates_version\": \"[^\"]*\"/\"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true
1487
- rm -f "${config_file}.tmp"
1503
+ sed_inplace "s/\"templates_version\": \"[^\"]*\"/\"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true
1488
1504
  fi
1489
1505
  fi
1490
1506
 
@@ -2019,9 +2035,9 @@ cmd_help() {
2019
2035
  echo " aidevops skill remove <name> # Remove an imported skill"
2020
2036
  echo ""
2021
2037
  echo "Installation:"
2022
- echo " npm install -g aidevops && aidevops update # via npm"
2038
+ echo " npm install -g aidevops && aidevops update # via npm (recommended)"
2023
2039
  echo " brew install marcusquinn/tap/aidevops && aidevops update # via Homebrew"
2024
- echo " bash <(curl -fsSL https://aidevops.sh) # via curl"
2040
+ echo " curl -fsSL https://aidevops.sh -o /tmp/aidevops-setup.sh && bash /tmp/aidevops-setup.sh # manual"
2025
2041
  echo ""
2026
2042
  echo "Documentation: https://github.com/marcusquinn/aidevops"
2027
2043
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "2.105.2",
3
+ "version": "2.105.5",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "bin": {
package/setup.sh CHANGED
@@ -3,11 +3,12 @@
3
3
  # AI Assistant Server Access Framework Setup Script
4
4
  # Helps developers set up the framework for their infrastructure
5
5
  #
6
- # Version: 2.105.2
6
+ # Version: 2.105.5
7
7
  #
8
- # Quick Install (one-liner):
9
- # bash <(curl -fsSL https://aidevops.dev/install)
10
- # OR: bash <(curl -fsSL https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh)
8
+ # Quick Install:
9
+ # npm install -g aidevops && aidevops update (recommended)
10
+ # brew install marcusquinn/tap/aidevops && aidevops update (Homebrew)
11
+ # curl -fsSL https://aidevops.sh -o /tmp/aidevops-setup.sh && bash /tmp/aidevops-setup.sh (manual)
11
12
 
12
13
  # Colors for output
13
14
  GREEN='\033[0;32m'
@@ -64,6 +65,73 @@ run_with_spinner() {
64
65
  return $exit_code
65
66
  }
66
67
 
68
+ # Verified install: download script to temp file, inspect, then execute
69
+ # Replaces unsafe curl|sh patterns with download-verify-execute
70
+ # Usage: verified_install "description" "url" [extra_args...]
71
+ # Options (set before calling):
72
+ # VERIFIED_INSTALL_SUDO="true" - run with sudo
73
+ # VERIFIED_INSTALL_SHELL="sh" - use sh instead of bash (default: bash)
74
+ # Returns: 0 on success, 1 on failure
75
+ verified_install() {
76
+ local description="$1"
77
+ local url="$2"
78
+ shift 2
79
+ local extra_args=("$@")
80
+ local shell="${VERIFIED_INSTALL_SHELL:-bash}"
81
+ local use_sudo="${VERIFIED_INSTALL_SUDO:-false}"
82
+
83
+ # Reset options for next call
84
+ VERIFIED_INSTALL_SUDO="false"
85
+ VERIFIED_INSTALL_SHELL="bash"
86
+
87
+ # Create secure temp file
88
+ local tmp_script
89
+ tmp_script=$(mktemp "${TMPDIR:-/tmp}/aidevops-install-XXXXXX.sh") || {
90
+ print_error "Failed to create temp file for $description"
91
+ return 1
92
+ }
93
+
94
+ # Ensure cleanup on exit from this function
95
+ # shellcheck disable=SC2064
96
+ trap "rm -f '$tmp_script'" RETURN
97
+
98
+ # Download script to file (not piped to shell)
99
+ print_info "Downloading $description install script..."
100
+ if ! curl -fsSL "$url" -o "$tmp_script" 2>/dev/null; then
101
+ print_error "Failed to download $description install script from $url"
102
+ return 1
103
+ fi
104
+
105
+ # Verify download is non-empty and looks like a script
106
+ if [[ ! -s "$tmp_script" ]]; then
107
+ print_error "Downloaded $description script is empty"
108
+ return 1
109
+ fi
110
+
111
+ # Basic content safety check: reject binary content
112
+ if file "$tmp_script" 2>/dev/null | grep -qv 'text'; then
113
+ print_error "Downloaded $description script appears to be binary, not a shell script"
114
+ return 1
115
+ fi
116
+
117
+ # Make executable
118
+ chmod +x "$tmp_script"
119
+
120
+ # Execute from file
121
+ local cmd=("$shell" "$tmp_script" "${extra_args[@]}")
122
+ if [[ "$use_sudo" == "true" ]]; then
123
+ cmd=(sudo "$shell" "$tmp_script" "${extra_args[@]}")
124
+ fi
125
+
126
+ if "${cmd[@]}"; then
127
+ print_success "$description installed"
128
+ return 0
129
+ else
130
+ print_error "$description installation failed"
131
+ return 1
132
+ fi
133
+ }
134
+
67
135
  # Find OpenCode config file (checks multiple possible locations)
68
136
  # Returns: path to config file, or empty string if not found
69
137
  find_opencode_config() {
@@ -310,20 +378,36 @@ migrate_agent_to_agents_folder() {
310
378
  done < <(jq -r '.initialized_repos[].path' "$repos_file" 2>/dev/null)
311
379
  fi
312
380
 
313
- # 2. Also scan ~/Git/ for any .agent symlinks not in repos.json
381
+ # 2. Also scan ~/Git/ for any .agent symlinks or directories not in repos.json
314
382
  if [[ -d "$HOME/Git" ]]; then
315
- while IFS= read -r -d '' agent_link; do
383
+ while IFS= read -r -d '' agent_path; do
316
384
  local repo_dir
317
- repo_dir=$(dirname "$agent_link")
318
- if [[ -L "$agent_link" ]] && [[ ! -e "$repo_dir/.agents" ]]; then
319
- local target
320
- target=$(readlink "$agent_link")
321
- rm -f "$agent_link"
322
- ln -s "$target" "$repo_dir/.agents" 2>/dev/null || true
323
- print_info " Migrated symlink: $agent_link -> .agents"
324
- ((migrated++))
385
+ repo_dir=$(dirname "$agent_path")
386
+
387
+ if [[ -L "$agent_path" ]]; then
388
+ # Symlink: migrate or clean up stale
389
+ if [[ ! -e "$repo_dir/.agents" ]]; then
390
+ local target
391
+ target=$(readlink "$agent_path")
392
+ rm -f "$agent_path"
393
+ ln -s "$target" "$repo_dir/.agents" 2>/dev/null || true
394
+ print_info " Migrated symlink: $agent_path -> .agents"
395
+ ((migrated++))
396
+ else
397
+ # .agents already exists, remove stale .agent symlink
398
+ rm -f "$agent_path"
399
+ print_info " Removed stale symlink: $agent_path (.agents already exists)"
400
+ ((migrated++))
401
+ fi
402
+ elif [[ -d "$agent_path" ]]; then
403
+ # Directory: rename to .agents if .agents doesn't exist
404
+ if [[ ! -e "$repo_dir/.agents" ]]; then
405
+ mv "$agent_path" "$repo_dir/.agents"
406
+ print_info " Renamed directory: $agent_path -> .agents"
407
+ ((migrated++))
408
+ fi
325
409
  fi
326
- done < <(find "$HOME/Git" -maxdepth 3 -name ".agent" -type l -print0 2>/dev/null)
410
+ done < <(find "$HOME/Git" -maxdepth 3 -name ".agent" \( -type l -o -type d \) -print0 2>/dev/null)
327
411
  fi
328
412
 
329
413
  # 3. Update AI assistant config files that reference .agent/
@@ -1211,8 +1295,9 @@ setup_oh_my_zsh() {
1211
1295
 
1212
1296
  if [[ "$install_omz" =~ ^[Yy]$ ]]; then
1213
1297
  print_info "Installing Oh My Zsh..."
1214
- # Use --unattended to avoid changing the shell or starting zsh
1215
- if sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended; then
1298
+ # Use verified download + --unattended to avoid changing the shell or starting zsh
1299
+ VERIFIED_INSTALL_SHELL="sh"
1300
+ if verified_install "Oh My Zsh" "https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh" --unattended; then
1216
1301
  print_success "Oh My Zsh installed"
1217
1302
 
1218
1303
  # Ensure .zshrc exists (Oh My Zsh creates it, but verify)
@@ -1235,11 +1320,11 @@ setup_oh_my_zsh() {
1235
1320
  fi
1236
1321
  else
1237
1322
  print_warning "Oh My Zsh installation failed"
1238
- print_info "Install manually: sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\""
1323
+ print_info "Install manually: curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -o /tmp/omz-install.sh && sh /tmp/omz-install.sh"
1239
1324
  fi
1240
1325
  else
1241
1326
  print_info "Skipped Oh My Zsh installation"
1242
- print_info "Install later: sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\""
1327
+ print_info "Install later: curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -o /tmp/omz-install.sh && sh /tmp/omz-install.sh"
1243
1328
  fi
1244
1329
 
1245
1330
  return 0
@@ -1720,15 +1805,17 @@ setup_recommended_tools() {
1720
1805
  pkg_manager=$(detect_package_manager)
1721
1806
  case "$pkg_manager" in
1722
1807
  apt)
1723
- # Add packagecloud repo for Tabby
1724
- print_info "Adding Tabby repository..."
1725
- curl -s https://packagecloud.io/install/repositories/eugeny/tabby/script.deb.sh | sudo bash
1726
- sudo apt-get install -y tabby-terminal
1808
+ # Add packagecloud repo for Tabby (verified download, not piped to sudo)
1809
+ VERIFIED_INSTALL_SUDO="true"
1810
+ if verified_install "Tabby repository (apt)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.deb.sh"; then
1811
+ sudo apt-get install -y tabby-terminal
1812
+ fi
1727
1813
  ;;
1728
1814
  dnf|yum)
1729
- print_info "Adding Tabby repository..."
1730
- curl -s https://packagecloud.io/install/repositories/eugeny/tabby/script.rpm.sh | sudo bash
1731
- sudo "$pkg_manager" install -y tabby-terminal
1815
+ VERIFIED_INSTALL_SUDO="true"
1816
+ if verified_install "Tabby repository (rpm)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.rpm.sh"; then
1817
+ sudo "$pkg_manager" install -y tabby-terminal
1818
+ fi
1732
1819
  ;;
1733
1820
  pacman)
1734
1821
  # AUR package
@@ -1764,10 +1851,9 @@ setup_recommended_tools() {
1764
1851
  echo " Download manually: https://zed.dev/download"
1765
1852
  fi
1766
1853
  elif [[ "$(uname)" == "Linux" ]]; then
1767
- # Zed provides an install script for Linux (interactive, can't use spinner)
1768
- print_info "Running Zed install script..."
1769
- if curl -f https://zed.dev/install.sh | sh; then
1770
- print_success "Zed installed successfully"
1854
+ # Zed provides an install script for Linux (verified download)
1855
+ VERIFIED_INSTALL_SHELL="sh"
1856
+ if verified_install "Zed" "https://zed.dev/install.sh"; then
1771
1857
  zed_installed=true
1772
1858
  else
1773
1859
  print_warning "Failed to install Zed"
@@ -1955,8 +2041,9 @@ add_local_bin_to_path() {
1955
2041
  while IFS= read -r rc_file; do
1956
2042
  [[ -z "$rc_file" ]] && continue
1957
2043
 
1958
- # Create the rc file if it doesn't exist
2044
+ # Create the rc file if it doesn't exist (ensure parent dir exists for fish etc.)
1959
2045
  if [[ ! -f "$rc_file" ]]; then
2046
+ mkdir -p "$(dirname "$rc_file")"
1960
2047
  touch "$rc_file"
1961
2048
  fi
1962
2049
 
@@ -2074,7 +2161,7 @@ alias aws-helper './.agents/scripts/aws-helper.sh'
2074
2161
  ALIASES
2075
2162
  )
2076
2163
 
2077
- # Check if aliases already exist in any rc file
2164
+ # Check if aliases already exist in any rc file (including fish config)
2078
2165
  local any_configured=false
2079
2166
  local rc_file
2080
2167
  while IFS= read -r rc_file; do
@@ -2084,6 +2171,13 @@ ALIASES
2084
2171
  break
2085
2172
  fi
2086
2173
  done < <(get_all_shell_rcs)
2174
+ # Also check fish config (not included in get_all_shell_rcs on macOS)
2175
+ if [[ "$any_configured" == "false" ]]; then
2176
+ local fish_config="$HOME/.config/fish/config.fish"
2177
+ if grep -q "# AI Assistant Server Access" "$fish_config" 2>/dev/null; then
2178
+ any_configured=true
2179
+ fi
2180
+ fi
2087
2181
 
2088
2182
  if [[ "$any_configured" == "true" ]]; then
2089
2183
  print_info "Server Access aliases already configured - Skipping"
@@ -2604,28 +2698,59 @@ scan_imported_skills() {
2604
2698
  return 0
2605
2699
  fi
2606
2700
 
2607
- # Install skill-scanner if not present and uv is available
2701
+ # Install skill-scanner if not present
2702
+ # Fallback chain: uv -> pipx -> venv+symlink -> pip3 --user (legacy)
2703
+ # PEP 668 (Ubuntu 24.04+) blocks pip3 --user, so we try isolated methods first
2608
2704
  if ! command -v skill-scanner &>/dev/null; then
2609
- if command -v uv &>/dev/null; then
2610
- print_info "Installing Cisco Skill Scanner..."
2705
+ local installed=false
2706
+
2707
+ # 1. uv tool install (preferred - fast, isolated)
2708
+ if [[ "$installed" == "false" ]] && command -v uv &>/dev/null; then
2709
+ print_info "Installing Cisco Skill Scanner via uv..."
2611
2710
  if run_with_spinner "Installing cisco-ai-skill-scanner" uv tool install cisco-ai-skill-scanner; then
2612
- print_success "Cisco Skill Scanner installed"
2613
- else
2614
- print_warning "Failed to install Cisco Skill Scanner - skipping security scan"
2615
- print_info "Install manually with: uv tool install cisco-ai-skill-scanner"
2616
- return 0
2711
+ print_success "Cisco Skill Scanner installed via uv"
2712
+ installed=true
2713
+ fi
2714
+ fi
2715
+
2716
+ # 2. pipx install (designed for isolated app installs)
2717
+ if [[ "$installed" == "false" ]] && command -v pipx &>/dev/null; then
2718
+ print_info "Installing Cisco Skill Scanner via pipx..."
2719
+ if run_with_spinner "Installing cisco-ai-skill-scanner" pipx install cisco-ai-skill-scanner; then
2720
+ print_success "Cisco Skill Scanner installed via pipx"
2721
+ installed=true
2617
2722
  fi
2618
- elif command -v pip3 &>/dev/null; then
2619
- print_info "Installing Cisco Skill Scanner via pip..."
2620
- if run_with_spinner "Installing cisco-ai-skill-scanner" pip3 install --user cisco-ai-skill-scanner; then
2621
- print_success "Cisco Skill Scanner installed"
2723
+ fi
2724
+
2725
+ # 3. venv + symlink (works on PEP 668 systems without uv/pipx)
2726
+ if [[ "$installed" == "false" ]] && command -v python3 &>/dev/null; then
2727
+ local venv_dir="$HOME/.aidevops/.agent-workspace/work/cisco-scanner-env"
2728
+ local bin_dir="$HOME/.local/bin"
2729
+ print_info "Installing Cisco Skill Scanner in isolated venv..."
2730
+ if python3 -m venv "$venv_dir" 2>/dev/null && \
2731
+ "$venv_dir/bin/pip" install cisco-ai-skill-scanner 2>/dev/null; then
2732
+ mkdir -p "$bin_dir"
2733
+ ln -sf "$venv_dir/bin/skill-scanner" "$bin_dir/skill-scanner"
2734
+ print_success "Cisco Skill Scanner installed via venv ($venv_dir)"
2735
+ installed=true
2622
2736
  else
2623
- print_warning "Failed to install Cisco Skill Scanner - skipping security scan"
2624
- return 0
2737
+ rm -rf "$venv_dir" 2>/dev/null || true
2625
2738
  fi
2626
- else
2627
- print_info "Cisco Skill Scanner not installed (uv or pip3 required)"
2628
- print_info "Install with: uv tool install cisco-ai-skill-scanner"
2739
+ fi
2740
+
2741
+ # 4. pip3 --user (legacy fallback, fails on PEP 668 systems)
2742
+ if [[ "$installed" == "false" ]] && command -v pip3 &>/dev/null; then
2743
+ print_info "Installing Cisco Skill Scanner via pip3 --user..."
2744
+ if run_with_spinner "Installing cisco-ai-skill-scanner" pip3 install --user cisco-ai-skill-scanner 2>/dev/null; then
2745
+ print_success "Cisco Skill Scanner installed via pip3"
2746
+ installed=true
2747
+ fi
2748
+ fi
2749
+
2750
+ if [[ "$installed" == "false" ]]; then
2751
+ print_warning "Failed to install Cisco Skill Scanner - skipping security scan"
2752
+ print_info "Install manually with: uv tool install cisco-ai-skill-scanner"
2753
+ print_info "Or: pipx install cisco-ai-skill-scanner"
2629
2754
  return 0
2630
2755
  fi
2631
2756
  fi
@@ -2904,7 +3029,7 @@ install_mcp_packages() {
2904
3029
  install_cmd="npm install -g"
2905
3030
  else
2906
3031
  print_warning "Neither bun nor npm found - cannot install MCP packages"
2907
- print_info "Install bun (recommended): curl -fsSL https://bun.sh/install | bash"
3032
+ print_info "Install bun (recommended): npm install -g bun OR brew install oven-sh/bun/bun"
2908
3033
  return 0
2909
3034
  fi
2910
3035
 
@@ -3335,15 +3460,15 @@ setup_beads_ui() {
3335
3460
  print_warning "Go install failed"
3336
3461
  fi
3337
3462
  else
3338
- # Offer curl install script
3463
+ # Offer verified install script (download-then-execute, not piped)
3339
3464
  read -r -p " Install bv via install script? [Y/n]: " use_script
3340
3465
  if [[ "$use_script" =~ ^[Yy]?$ ]]; then
3341
- if run_with_spinner "Installing bv via script" bash -c 'curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/beads_viewer/main/install.sh" | bash'; then
3466
+ if verified_install "bv (beads viewer)" "https://raw.githubusercontent.com/Dicklesworthstone/beads_viewer/main/install.sh"; then
3342
3467
  print_info "Run: bv (in a beads-enabled project)"
3343
3468
  ((installed_count++))
3344
3469
  else
3345
3470
  print_warning "Install script failed - try manually:"
3346
- print_info " curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/beads_viewer/main/install.sh | bash"
3471
+ print_info " Homebrew: brew tap dicklesworthstone/tap && brew install dicklesworthstone/tap/bv"
3347
3472
  fi
3348
3473
  else
3349
3474
  print_info "Install later:"
@@ -3417,7 +3542,7 @@ setup_browser_tools() {
3417
3542
  # Install Bun if not present (required for dev-browser)
3418
3543
  if [[ "$has_bun" == "false" ]]; then
3419
3544
  print_info "Installing Bun (required for dev-browser)..."
3420
- if curl -fsSL https://bun.sh/install | bash 2>/dev/null; then
3545
+ if verified_install "Bun" "https://bun.sh/install"; then
3421
3546
  # Source the updated PATH
3422
3547
  export BUN_INSTALL="$HOME/.bun"
3423
3548
  export PATH="$BUN_INSTALL/bin:$PATH"
@@ -3670,14 +3795,6 @@ setup_opencode_plugins() {
3670
3795
 
3671
3796
  return 0
3672
3797
  }
3673
-
3674
- # Setup Oh-My-OpenCode Plugin (removed - no longer supported)
3675
- # Kept as stub for backward compatibility with any callers
3676
- setup_oh_my_opencode() {
3677
- # Removed - oh-my-opencode no longer supported
3678
- return 0
3679
- }
3680
-
3681
3798
  setup_seo_mcps() {
3682
3799
  print_info "Setting up SEO integrations..."
3683
3800