aidevops 3.1.139 → 3.1.142

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.
@@ -95,6 +95,81 @@ setup_git_clis() {
95
95
  return 0
96
96
  }
97
97
 
98
+ _print_file_discovery_manual_install() {
99
+ echo ""
100
+ echo " Manual installation:"
101
+ echo " macOS: brew install fd ripgrep ripgrep-all"
102
+ echo " Ubuntu/Debian: sudo apt install fd-find ripgrep # rga: cargo install ripgrep_all"
103
+ echo " Fedora: sudo dnf install fd-find ripgrep # rga: cargo install ripgrep_all"
104
+ echo " Arch: sudo pacman -S fd ripgrep ripgrep-all"
105
+ return 0
106
+ }
107
+
108
+ # Add fd=fdfind alias to shell rc files on Debian/Ubuntu after apt install.
109
+ _add_fd_alias_debian() {
110
+ local rc_files=("$HOME/.bashrc" "$HOME/.zshrc")
111
+ local added_to=""
112
+ local rc_file
113
+
114
+ for rc_file in "${rc_files[@]}"; do
115
+ [[ ! -f "$rc_file" ]] && continue
116
+
117
+ if ! grep -q 'alias fd="fdfind"' "$rc_file" 2>/dev/null; then
118
+ if { echo '' >>"$rc_file" &&
119
+ echo '# fd-find alias for Debian/Ubuntu (added by aidevops)' >>"$rc_file" &&
120
+ echo 'alias fd="fdfind"' >>"$rc_file"; }; then
121
+ added_to="${added_to:+$added_to, }$rc_file"
122
+ fi
123
+ fi
124
+ done
125
+
126
+ if [[ -n "$added_to" ]]; then
127
+ print_success "Added alias fd=fdfind to: $added_to"
128
+ echo " Restart your shell to activate"
129
+ else
130
+ print_success "fd alias already configured"
131
+ fi
132
+ return 0
133
+ }
134
+
135
+ # Resolve apt package names (fd→fd-find on Debian/Ubuntu) and install.
136
+ _install_file_discovery_packages() {
137
+ local pkg_manager="$1"
138
+ shift
139
+ local missing_packages=("$@")
140
+
141
+ print_info "Installing ${missing_packages[*]}..."
142
+
143
+ local actual_packages=()
144
+ local pkg
145
+ for pkg in "${missing_packages[@]}"; do
146
+ case "$pkg_manager" in
147
+ apt)
148
+ # Debian/Ubuntu uses fd-find instead of fd
149
+ if [[ "$pkg" == "fd" ]]; then
150
+ actual_packages+=("fd-find")
151
+ else
152
+ actual_packages+=("$pkg")
153
+ fi
154
+ ;;
155
+ *)
156
+ actual_packages+=("$pkg")
157
+ ;;
158
+ esac
159
+ done
160
+
161
+ if install_packages "$pkg_manager" "${actual_packages[@]}"; then
162
+ print_success "File discovery tools installed"
163
+ # On Debian/Ubuntu, fd is installed as fdfind — create alias in shell rc files
164
+ if [[ "$pkg_manager" == "apt" ]] && command -v fdfind >/dev/null 2>&1 && ! command -v fd >/dev/null 2>&1; then
165
+ _add_fd_alias_debian
166
+ fi
167
+ else
168
+ print_warning "Failed to install some file discovery tools (non-critical)"
169
+ fi
170
+ return 0
171
+ }
172
+
98
173
  setup_file_discovery_tools() {
99
174
  print_info "Setting up file discovery tools..."
100
175
 
@@ -160,72 +235,13 @@ setup_file_discovery_tools() {
160
235
  fi
161
236
 
162
237
  if [[ "$install_fd_tools" =~ ^[Yy]?$ ]]; then
163
- print_info "Installing ${missing_packages[*]}..."
164
-
165
- # Handle package name differences across package managers
166
- local actual_packages=()
167
- for pkg in "${missing_packages[@]}"; do
168
- case "$pkg_manager" in
169
- apt)
170
- # Debian/Ubuntu uses fd-find instead of fd
171
- if [[ "$pkg" == "fd" ]]; then
172
- actual_packages+=("fd-find")
173
- else
174
- actual_packages+=("$pkg")
175
- fi
176
- ;;
177
- *)
178
- actual_packages+=("$pkg")
179
- ;;
180
- esac
181
- done
182
-
183
- if install_packages "$pkg_manager" "${actual_packages[@]}"; then
184
- print_success "File discovery tools installed"
185
-
186
- # On Debian/Ubuntu, fd is installed as fdfind - create alias in all existing shell rc files
187
- if [[ "$pkg_manager" == "apt" ]] && command -v fdfind >/dev/null 2>&1 && ! command -v fd >/dev/null 2>&1; then
188
- local rc_files=("$HOME/.bashrc" "$HOME/.zshrc")
189
- local added_to=""
190
-
191
- for rc_file in "${rc_files[@]}"; do
192
- [[ ! -f "$rc_file" ]] && continue
193
-
194
- if ! grep -q 'alias fd="fdfind"' "$rc_file" 2>/dev/null; then
195
- if { echo '' >>"$rc_file" &&
196
- echo '# fd-find alias for Debian/Ubuntu (added by aidevops)' >>"$rc_file" &&
197
- echo 'alias fd="fdfind"' >>"$rc_file"; }; then
198
- added_to="${added_to:+$added_to, }$rc_file"
199
- fi
200
- fi
201
- done
202
-
203
- if [[ -n "$added_to" ]]; then
204
- print_success "Added alias fd=fdfind to: $added_to"
205
- echo " Restart your shell to activate"
206
- else
207
- print_success "fd alias already configured"
208
- fi
209
- fi
210
- else
211
- print_warning "Failed to install some file discovery tools (non-critical)"
212
- fi
238
+ _install_file_discovery_packages "$pkg_manager" "${missing_packages[@]}"
213
239
  else
214
240
  print_info "Skipped file discovery tools installation"
215
- echo ""
216
- echo " Manual installation:"
217
- echo " macOS: brew install fd ripgrep ripgrep-all"
218
- echo " Ubuntu/Debian: sudo apt install fd-find ripgrep # rga: cargo install ripgrep_all"
219
- echo " Fedora: sudo dnf install fd-find ripgrep # rga: cargo install ripgrep_all"
220
- echo " Arch: sudo pacman -S fd ripgrep ripgrep-all"
241
+ _print_file_discovery_manual_install
221
242
  fi
222
243
  else
223
- echo ""
224
- echo " Manual installation:"
225
- echo " macOS: brew install fd ripgrep ripgrep-all"
226
- echo " Ubuntu/Debian: sudo apt install fd-find ripgrep # rga: cargo install ripgrep_all"
227
- echo " Fedora: sudo dnf install fd-find ripgrep # rga: cargo install ripgrep_all"
228
- echo " Arch: sudo pacman -S fd ripgrep ripgrep-all"
244
+ _print_file_discovery_manual_install
229
245
  fi
230
246
  else
231
247
  print_success "All file discovery tools installed!"
@@ -569,6 +585,87 @@ setup_rosetta_audit() {
569
585
  return 0
570
586
  }
571
587
 
588
+ # Install Worktrunk shell integration (enables 'wt switch' to change directories).
589
+ _setup_worktrunk_shell_integration() {
590
+ print_info "Installing shell integration..."
591
+ if wt config shell install; then
592
+ print_success "Shell integration installed"
593
+ print_info "Restart your terminal or source your shell config"
594
+ else
595
+ print_warning "Shell integration failed - run manually: wt config shell install"
596
+ fi
597
+ return 0
598
+ }
599
+
600
+ # Check and optionally install Worktrunk shell integration when wt is already present.
601
+ _check_worktrunk_shell_integration() {
602
+ local wt_integrated=false
603
+ local rc_file
604
+ while IFS= read -r rc_file; do
605
+ [[ -z "$rc_file" ]] && continue
606
+ if [[ -f "$rc_file" ]] && grep -q "worktrunk" "$rc_file" 2>/dev/null; then
607
+ wt_integrated=true
608
+ break
609
+ fi
610
+ done < <(get_all_shell_rcs)
611
+
612
+ if [[ "$wt_integrated" == "false" ]]; then
613
+ print_info "Shell integration not detected"
614
+ local install_shell
615
+ read -r -p "Install Worktrunk shell integration (enables 'wt switch' to change directories)? [Y/n]: " install_shell
616
+ if [[ "$install_shell" =~ ^[Yy]?$ ]]; then
617
+ _setup_worktrunk_shell_integration
618
+ fi
619
+ else
620
+ print_success "Shell integration already configured"
621
+ fi
622
+ return 0
623
+ }
624
+
625
+ # Install Worktrunk via Homebrew and set up shell integration.
626
+ _install_worktrunk_brew() {
627
+ local install_wt
628
+ read -r -p "Install Worktrunk via Homebrew? [Y/n]: " install_wt
629
+
630
+ if [[ "$install_wt" =~ ^[Yy]?$ ]]; then
631
+ if run_with_spinner "Installing Worktrunk via Homebrew" brew install max-sixty/worktrunk/wt; then
632
+ _setup_worktrunk_shell_integration
633
+ echo ""
634
+ print_info "Quick start:"
635
+ echo " wt switch feature/my-feature # Create/switch to worktree"
636
+ echo " wt list # List all worktrees"
637
+ echo " wt merge # Merge and cleanup"
638
+ echo ""
639
+ print_info "Documentation: ~/.aidevops/agents/tools/git/worktrunk.md"
640
+ else
641
+ print_warning "Homebrew installation failed"
642
+ echo " Try: cargo install worktrunk && wt config shell install"
643
+ fi
644
+ else
645
+ print_info "Skipped Worktrunk installation"
646
+ print_info "Install later: brew install max-sixty/worktrunk/wt"
647
+ print_info "Fallback available: ~/.aidevops/agents/scripts/worktree-helper.sh"
648
+ fi
649
+ return 0
650
+ }
651
+
652
+ # Install Worktrunk via Cargo and set up shell integration.
653
+ _install_worktrunk_cargo() {
654
+ local install_wt
655
+ read -r -p "Install Worktrunk via Cargo? [Y/n]: " install_wt
656
+
657
+ if [[ "$install_wt" =~ ^[Yy]?$ ]]; then
658
+ if run_with_spinner "Installing Worktrunk via Cargo" cargo install worktrunk; then
659
+ _setup_worktrunk_shell_integration
660
+ else
661
+ print_warning "Cargo installation failed"
662
+ fi
663
+ else
664
+ print_info "Skipped Worktrunk installation"
665
+ fi
666
+ return 0
667
+ }
668
+
572
669
  setup_worktrunk() {
573
670
  print_info "Setting up Worktrunk (git worktree management)..."
574
671
 
@@ -577,33 +674,7 @@ setup_worktrunk() {
577
674
  local wt_version
578
675
  wt_version=$(wt --version 2>/dev/null | head -1 || echo "unknown")
579
676
  print_success "Worktrunk already installed: $wt_version"
580
-
581
- # Check if shell integration is installed (check all rc files)
582
- local wt_integrated=false
583
- local rc_file
584
- while IFS= read -r rc_file; do
585
- [[ -z "$rc_file" ]] && continue
586
- if [[ -f "$rc_file" ]] && grep -q "worktrunk" "$rc_file" 2>/dev/null; then
587
- wt_integrated=true
588
- break
589
- fi
590
- done < <(get_all_shell_rcs)
591
-
592
- if [[ "$wt_integrated" == "false" ]]; then
593
- print_info "Shell integration not detected"
594
- read -r -p "Install Worktrunk shell integration (enables 'wt switch' to change directories)? [Y/n]: " install_shell
595
- if [[ "$install_shell" =~ ^[Yy]?$ ]]; then
596
- print_info "Installing shell integration..."
597
- if wt config shell install; then
598
- print_success "Shell integration installed"
599
- print_info "Restart your terminal for the change to take effect"
600
- else
601
- print_warning "Shell integration failed - run manually: wt config shell install"
602
- fi
603
- fi
604
- else
605
- print_success "Shell integration already configured"
606
- fi
677
+ _check_worktrunk_shell_integration
607
678
  return 0
608
679
  fi
609
680
 
@@ -621,54 +692,9 @@ setup_worktrunk() {
621
692
  pkg_manager=$(detect_package_manager)
622
693
 
623
694
  if [[ "$pkg_manager" == "brew" ]]; then
624
- read -r -p "Install Worktrunk via Homebrew? [Y/n]: " install_wt
625
-
626
- if [[ "$install_wt" =~ ^[Yy]?$ ]]; then
627
- if run_with_spinner "Installing Worktrunk via Homebrew" brew install max-sixty/worktrunk/wt; then
628
- # Install shell integration (don't use spinner - command is fast and may need interaction)
629
- print_info "Installing shell integration..."
630
- if wt config shell install; then
631
- print_success "Shell integration installed"
632
- print_info "Restart your terminal or source your shell config"
633
- else
634
- print_warning "Shell integration failed - run manually: wt config shell install"
635
- fi
636
-
637
- echo ""
638
- print_info "Quick start:"
639
- echo " wt switch feature/my-feature # Create/switch to worktree"
640
- echo " wt list # List all worktrees"
641
- echo " wt merge # Merge and cleanup"
642
- echo ""
643
- print_info "Documentation: ~/.aidevops/agents/tools/git/worktrunk.md"
644
- else
645
- print_warning "Homebrew installation failed"
646
- echo " Try: cargo install worktrunk && wt config shell install"
647
- fi
648
- else
649
- print_info "Skipped Worktrunk installation"
650
- print_info "Install later: brew install max-sixty/worktrunk/wt"
651
- print_info "Fallback available: ~/.aidevops/agents/scripts/worktree-helper.sh"
652
- fi
695
+ _install_worktrunk_brew
653
696
  elif command -v cargo >/dev/null 2>&1; then
654
- read -r -p "Install Worktrunk via Cargo? [Y/n]: " install_wt
655
-
656
- if [[ "$install_wt" =~ ^[Yy]?$ ]]; then
657
- if run_with_spinner "Installing Worktrunk via Cargo" cargo install worktrunk; then
658
- # Install shell integration (don't use spinner - command is fast and may need interaction)
659
- print_info "Installing shell integration..."
660
- if wt config shell install; then
661
- print_success "Shell integration installed"
662
- print_info "Restart your terminal or source your shell config"
663
- else
664
- print_warning "Shell integration failed - run manually: wt config shell install"
665
- fi
666
- else
667
- print_warning "Cargo installation failed"
668
- fi
669
- else
670
- print_info "Skipped Worktrunk installation"
671
- fi
697
+ _install_worktrunk_cargo
672
698
  else
673
699
  print_warning "Worktrunk not installed"
674
700
  echo ""
@@ -685,6 +711,167 @@ setup_worktrunk() {
685
711
  return 0
686
712
  }
687
713
 
714
+ # Trigger OpenCode extension install in Zed via the zed:// URI scheme.
715
+ _install_opencode_ext_for_zed() {
716
+ local install_opencode_ext
717
+ read -r -p "Install OpenCode extension for Zed? [Y/n]: " install_opencode_ext
718
+ if [[ "$install_opencode_ext" =~ ^[Yy]?$ ]]; then
719
+ print_info "Installing OpenCode extension..."
720
+ if [[ "$(uname)" == "Darwin" ]]; then
721
+ open "zed://extension/opencode" 2>/dev/null
722
+ print_success "OpenCode extension install triggered"
723
+ print_info "Zed will open and prompt to install the extension"
724
+ elif [[ "$(uname)" == "Linux" ]]; then
725
+ xdg-open "zed://extension/opencode" 2>/dev/null ||
726
+ print_info "Open Zed and install 'opencode' from Extensions (Cmd+Shift+X)"
727
+ fi
728
+ fi
729
+ return 0
730
+ }
731
+
732
+ # Install Tabby terminal on Linux (x86_64 only via packagecloud; ARM64 manual).
733
+ _install_tabby_linux() {
734
+ local arch
735
+ arch=$(uname -m)
736
+ # Tabby packagecloud repo only has x86_64 packages
737
+ # ARM64 (aarch64) must use .deb from GitHub releases or skip
738
+ if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
739
+ # Clean up stale Tabby packagecloud repo if it exists from a previous run
740
+ # (it causes apt-get update failures on ARM64)
741
+ if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
742
+ print_info "Removing stale Tabby packagecloud repo (not available for ARM64)..."
743
+ sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
744
+ sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
745
+ sudo apt-get update -qq 2>/dev/null || true
746
+ fi
747
+ print_warning "Tabby packages are not available for ARM64 Linux via package manager"
748
+ echo " Download ARM64 .deb from: https://github.com/Eugeny/tabby/releases/latest"
749
+ echo " Or skip Tabby - it's optional (a modern terminal emulator)"
750
+ return 0
751
+ fi
752
+
753
+ local pkg_manager
754
+ pkg_manager=$(detect_package_manager)
755
+ case "$pkg_manager" in
756
+ apt)
757
+ # Add packagecloud repo for Tabby (verified download, not piped to sudo)
758
+ # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
759
+ VERIFIED_INSTALL_SUDO="true"
760
+ if verified_install "Tabby repository (apt)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.deb.sh"; then
761
+ if ! sudo apt-get install -y tabby-terminal; then
762
+ print_warning "Tabby package not found for this architecture"
763
+ echo " Download from: https://github.com/Eugeny/tabby/releases/latest"
764
+ fi
765
+ fi
766
+ ;;
767
+ dnf | yum)
768
+ # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
769
+ VERIFIED_INSTALL_SUDO="true"
770
+ if verified_install "Tabby repository (rpm)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.rpm.sh"; then
771
+ if ! sudo "$pkg_manager" install -y tabby-terminal; then
772
+ print_warning "Tabby package not found for this architecture"
773
+ echo " Download from: https://github.com/Eugeny/tabby/releases/latest"
774
+ fi
775
+ fi
776
+ ;;
777
+ pacman)
778
+ # AUR package
779
+ print_info "Tabby available in AUR as 'tabby-bin'"
780
+ echo " Install with: yay -S tabby-bin"
781
+ ;;
782
+ *)
783
+ echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
784
+ ;;
785
+ esac
786
+ return 0
787
+ }
788
+
789
+ # Offer and perform Tabby terminal installation.
790
+ _install_tabby() {
791
+ local install_tabby
792
+ read -r -p "Install Tabby terminal? [Y/n]: " install_tabby
793
+
794
+ if [[ "$install_tabby" =~ ^[Yy]?$ ]]; then
795
+ if [[ "$(uname)" == "Darwin" ]]; then
796
+ if command -v brew >/dev/null 2>&1; then
797
+ if run_with_spinner "Installing Tabby" brew install --cask tabby; then
798
+ : # Success message handled by spinner
799
+ else
800
+ print_warning "Failed to install Tabby via Homebrew"
801
+ echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
802
+ fi
803
+ else
804
+ print_warning "Homebrew not found"
805
+ echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
806
+ fi
807
+ elif [[ "$(uname)" == "Linux" ]]; then
808
+ _install_tabby_linux
809
+ fi
810
+ else
811
+ print_info "Skipped Tabby installation"
812
+ fi
813
+ return 0
814
+ }
815
+
816
+ # Offer and perform Zed editor installation, then optionally install OpenCode extension.
817
+ _install_zed_and_opencode_ext() {
818
+ local install_zed
819
+ read -r -p "Install Zed editor? [Y/n]: " install_zed
820
+
821
+ if [[ "$install_zed" =~ ^[Yy]?$ ]]; then
822
+ local zed_installed=false
823
+ if [[ "$(uname)" == "Darwin" ]]; then
824
+ if command -v brew >/dev/null 2>&1; then
825
+ if run_with_spinner "Installing Zed" brew install --cask zed; then
826
+ zed_installed=true
827
+ else
828
+ print_warning "Failed to install Zed via Homebrew"
829
+ echo " Download manually: https://zed.dev/download"
830
+ fi
831
+ else
832
+ print_warning "Homebrew not found"
833
+ echo " Download manually: https://zed.dev/download"
834
+ fi
835
+ elif [[ "$(uname)" == "Linux" ]]; then
836
+ # Zed provides an install script for Linux (verified download)
837
+ # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
838
+ VERIFIED_INSTALL_SHELL="sh"
839
+ if verified_install "Zed" "https://zed.dev/install.sh"; then
840
+ zed_installed=true
841
+ else
842
+ print_warning "Failed to install Zed"
843
+ echo " See: https://zed.dev/docs/linux"
844
+ fi
845
+ fi
846
+
847
+ if [[ "$zed_installed" == "true" ]]; then
848
+ _install_opencode_ext_for_zed
849
+ fi
850
+ else
851
+ print_info "Skipped Zed installation"
852
+ fi
853
+ return 0
854
+ }
855
+
856
+ # Check for OpenCode extension in an existing Zed installation and offer to install.
857
+ _check_opencode_ext_existing_zed() {
858
+ local zed_extensions_dir=""
859
+ if [[ "$(uname)" == "Darwin" ]]; then
860
+ zed_extensions_dir="$HOME/Library/Application Support/Zed/extensions/installed"
861
+ elif [[ "$(uname)" == "Linux" ]]; then
862
+ zed_extensions_dir="$HOME/.local/share/zed/extensions/installed"
863
+ fi
864
+
865
+ if [[ -d "$zed_extensions_dir" ]]; then
866
+ if [[ ! -d "$zed_extensions_dir/opencode" ]]; then
867
+ _install_opencode_ext_for_zed
868
+ else
869
+ print_success "OpenCode extension already installed in Zed"
870
+ fi
871
+ fi
872
+ return 0
873
+ }
874
+
688
875
  setup_recommended_tools() {
689
876
  print_info "Checking recommended development tools..."
690
877
 
@@ -734,31 +921,7 @@ setup_recommended_tools() {
734
921
 
735
922
  # Check for OpenCode extension in existing Zed installation
736
923
  if [[ "$zed_exists" == "true" ]]; then
737
- local zed_extensions_dir=""
738
- if [[ "$(uname)" == "Darwin" ]]; then
739
- zed_extensions_dir="$HOME/Library/Application Support/Zed/extensions/installed"
740
- elif [[ "$(uname)" == "Linux" ]]; then
741
- zed_extensions_dir="$HOME/.local/share/zed/extensions/installed"
742
- fi
743
-
744
- if [[ -d "$zed_extensions_dir" ]]; then
745
- if [[ ! -d "$zed_extensions_dir/opencode" ]]; then
746
- read -r -p "Install OpenCode extension for Zed? [Y/n]: " install_opencode_ext
747
- if [[ "$install_opencode_ext" =~ ^[Yy]?$ ]]; then
748
- print_info "Installing OpenCode extension..."
749
- if [[ "$(uname)" == "Darwin" ]]; then
750
- open "zed://extension/opencode" 2>/dev/null
751
- print_success "OpenCode extension install triggered"
752
- print_info "Zed will open and prompt to install the extension"
753
- elif [[ "$(uname)" == "Linux" ]]; then
754
- xdg-open "zed://extension/opencode" 2>/dev/null ||
755
- print_info "Open Zed and install 'opencode' from Extensions"
756
- fi
757
- fi
758
- else
759
- print_success "OpenCode extension already installed in Zed"
760
- fi
761
- fi
924
+ _check_opencode_ext_existing_zed
762
925
  fi
763
926
 
764
927
  # Offer to install missing tools
@@ -770,127 +933,12 @@ setup_recommended_tools() {
770
933
 
771
934
  # Install Tabby if missing
772
935
  if [[ " ${missing_tools[*]} " =~ " tabby " ]]; then
773
- read -r -p "Install Tabby terminal? [Y/n]: " install_tabby
774
-
775
- if [[ "$install_tabby" =~ ^[Yy]?$ ]]; then
776
- if [[ "$(uname)" == "Darwin" ]]; then
777
- if command -v brew >/dev/null 2>&1; then
778
- if run_with_spinner "Installing Tabby" brew install --cask tabby; then
779
- : # Success message handled by spinner
780
- else
781
- print_warning "Failed to install Tabby via Homebrew"
782
- echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
783
- fi
784
- else
785
- print_warning "Homebrew not found"
786
- echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
787
- fi
788
- elif [[ "$(uname)" == "Linux" ]]; then
789
- local arch
790
- arch=$(uname -m)
791
- # Tabby packagecloud repo only has x86_64 packages
792
- # ARM64 (aarch64) must use .deb from GitHub releases or skip
793
- if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
794
- # Clean up stale Tabby packagecloud repo if it exists from a previous run
795
- # (it causes apt-get update failures on ARM64)
796
- if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
797
- print_info "Removing stale Tabby packagecloud repo (not available for ARM64)..."
798
- sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
799
- sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
800
- sudo apt-get update -qq 2>/dev/null || true
801
- fi
802
- print_warning "Tabby packages are not available for ARM64 Linux via package manager"
803
- echo " Download ARM64 .deb from: https://github.com/Eugeny/tabby/releases/latest"
804
- echo " Or skip Tabby - it's optional (a modern terminal emulator)"
805
- else
806
- local pkg_manager
807
- pkg_manager=$(detect_package_manager)
808
- case "$pkg_manager" in
809
- apt)
810
- # Add packagecloud repo for Tabby (verified download, not piped to sudo)
811
- # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
812
- VERIFIED_INSTALL_SUDO="true"
813
- if verified_install "Tabby repository (apt)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.deb.sh"; then
814
- if ! sudo apt-get install -y tabby-terminal; then
815
- print_warning "Tabby package not found for this architecture"
816
- echo " Download from: https://github.com/Eugeny/tabby/releases/latest"
817
- fi
818
- fi
819
- ;;
820
- dnf | yum)
821
- # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
822
- VERIFIED_INSTALL_SUDO="true"
823
- if verified_install "Tabby repository (rpm)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.rpm.sh"; then
824
- if ! sudo "$pkg_manager" install -y tabby-terminal; then
825
- print_warning "Tabby package not found for this architecture"
826
- echo " Download from: https://github.com/Eugeny/tabby/releases/latest"
827
- fi
828
- fi
829
- ;;
830
- pacman)
831
- # AUR package
832
- print_info "Tabby available in AUR as 'tabby-bin'"
833
- echo " Install with: yay -S tabby-bin"
834
- ;;
835
- *)
836
- echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
837
- ;;
838
- esac
839
- fi
840
- fi
841
- else
842
- print_info "Skipped Tabby installation"
843
- fi
936
+ _install_tabby
844
937
  fi
845
938
 
846
939
  # Install Zed if missing
847
940
  if [[ " ${missing_tools[*]} " =~ " zed " ]]; then
848
- read -r -p "Install Zed editor? [Y/n]: " install_zed
849
-
850
- if [[ "$install_zed" =~ ^[Yy]?$ ]]; then
851
- local zed_installed=false
852
- if [[ "$(uname)" == "Darwin" ]]; then
853
- if command -v brew >/dev/null 2>&1; then
854
- if run_with_spinner "Installing Zed" brew install --cask zed; then
855
- zed_installed=true
856
- else
857
- print_warning "Failed to install Zed via Homebrew"
858
- echo " Download manually: https://zed.dev/download"
859
- fi
860
- else
861
- print_warning "Homebrew not found"
862
- echo " Download manually: https://zed.dev/download"
863
- fi
864
- elif [[ "$(uname)" == "Linux" ]]; then
865
- # Zed provides an install script for Linux (verified download)
866
- # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
867
- VERIFIED_INSTALL_SHELL="sh"
868
- if verified_install "Zed" "https://zed.dev/install.sh"; then
869
- zed_installed=true
870
- else
871
- print_warning "Failed to install Zed"
872
- echo " See: https://zed.dev/docs/linux"
873
- fi
874
- fi
875
-
876
- # Install OpenCode extension for Zed
877
- if [[ "$zed_installed" == "true" ]]; then
878
- read -r -p "Install OpenCode extension for Zed? [Y/n]: " install_opencode_ext
879
- if [[ "$install_opencode_ext" =~ ^[Yy]?$ ]]; then
880
- print_info "Installing OpenCode extension..."
881
- if [[ "$(uname)" == "Darwin" ]]; then
882
- open "zed://extension/opencode" 2>/dev/null
883
- print_success "OpenCode extension install triggered"
884
- print_info "Zed will open and prompt to install the extension"
885
- elif [[ "$(uname)" == "Linux" ]]; then
886
- xdg-open "zed://extension/opencode" 2>/dev/null ||
887
- print_info "Open Zed and install 'opencode' from Extensions (Cmd+Shift+X)"
888
- fi
889
- fi
890
- fi
891
- else
892
- print_info "Skipped Zed installation"
893
- fi
941
+ _install_zed_and_opencode_ext
894
942
  fi
895
943
  else
896
944
  print_success "All recommended tools installed!"
@@ -1294,24 +1342,76 @@ setup_nodejs_env() {
1294
1342
  fi
1295
1343
  }
1296
1344
 
1345
+ # Install Node.js via apt, preferring NodeSource LTS over the distro package.
1346
+ _install_nodejs_apt() {
1347
+ # Clean up stale Tabby packagecloud repo if present (causes apt-get update failures)
1348
+ if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
1349
+ local arch
1350
+ arch=$(uname -m)
1351
+ if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
1352
+ print_info "Removing stale Tabby repo (not available for ARM64)..."
1353
+ sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
1354
+ sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
1355
+ fi
1356
+ fi
1357
+
1358
+ # Use NodeSource for a recent version (apt default may be old)
1359
+ print_info "Installing Node.js (via NodeSource for latest LTS)..."
1360
+ if command -v curl >/dev/null 2>&1; then
1361
+ # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
1362
+ VERIFIED_INSTALL_SUDO="true"
1363
+ if verified_install "NodeSource repository" "https://deb.nodesource.com/setup_22.x"; then
1364
+ # Install nodejs (NodeSource bundles npm, but distro fallback may not)
1365
+ # Include npm explicitly in case NodeSource setup failed silently
1366
+ # and apt falls back to the distro nodejs package (which lacks npm)
1367
+ if sudo apt-get install -y nodejs npm 2>/dev/null || sudo apt-get install -y nodejs; then
1368
+ print_success "Node.js installed: $(node --version)"
1369
+ else
1370
+ print_warning "Node.js installation failed"
1371
+ fi
1372
+ else
1373
+ # Fallback to distro package
1374
+ print_info "Falling back to distro Node.js package..."
1375
+ if sudo apt-get install -y nodejs npm; then
1376
+ print_success "Node.js installed: $(node --version)"
1377
+ else
1378
+ print_warning "Node.js installation failed"
1379
+ fi
1380
+ fi
1381
+ else
1382
+ if sudo apt-get install -y nodejs npm; then
1383
+ print_success "Node.js installed: $(node --version)"
1384
+ else
1385
+ print_warning "Node.js installation failed"
1386
+ fi
1387
+ fi
1388
+ return 0
1389
+ }
1390
+
1391
+ # Ensure npm is present when Node.js is already installed (distro packages may omit it).
1392
+ _ensure_npm_installed() {
1393
+ if command -v npm >/dev/null 2>&1; then
1394
+ return 0
1395
+ fi
1396
+ print_info "npm not found (distro nodejs package may omit it) — installing..."
1397
+ local pkg_manager
1398
+ pkg_manager=$(detect_package_manager)
1399
+ case "$pkg_manager" in
1400
+ apt) sudo apt-get install -y npm 2>/dev/null || print_warning "Failed to install npm via apt" ;;
1401
+ dnf | yum) sudo "$pkg_manager" install -y npm 2>/dev/null || print_warning "Failed to install npm via $pkg_manager" ;;
1402
+ brew) brew install npm 2>/dev/null || print_warning "Failed to install npm via brew" ;;
1403
+ *) print_warning "Cannot auto-install npm — install manually" ;;
1404
+ esac
1405
+ return 0
1406
+ }
1407
+
1297
1408
  setup_nodejs() {
1298
1409
  # Check if Node.js is already installed
1299
1410
  if command -v node >/dev/null 2>&1; then
1300
1411
  local node_version
1301
1412
  node_version=$(node --version 2>/dev/null || echo "unknown")
1302
1413
  print_success "Node.js already installed: $node_version"
1303
- # Distro nodejs package may not include npm — install it if missing
1304
- if ! command -v npm >/dev/null 2>&1; then
1305
- print_info "npm not found (distro nodejs package may omit it) — installing..."
1306
- local pkg_manager
1307
- pkg_manager=$(detect_package_manager)
1308
- case "$pkg_manager" in
1309
- apt) sudo apt-get install -y npm 2>/dev/null || print_warning "Failed to install npm via apt" ;;
1310
- dnf | yum) sudo "$pkg_manager" install -y npm 2>/dev/null || print_warning "Failed to install npm via $pkg_manager" ;;
1311
- brew) brew install npm 2>/dev/null || print_warning "Failed to install npm via brew" ;;
1312
- *) print_warning "Cannot auto-install npm — install manually" ;;
1313
- esac
1314
- fi
1414
+ _ensure_npm_installed
1315
1415
  return 0
1316
1416
  fi
1317
1417
 
@@ -1322,6 +1422,7 @@ setup_nodejs() {
1322
1422
 
1323
1423
  case "$pkg_manager" in
1324
1424
  brew)
1425
+ local install_node
1325
1426
  read -r -p "Install Node.js via Homebrew? [Y/n]: " install_node
1326
1427
  if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1327
1428
  if run_with_spinner "Installing Node.js" brew install node; then
@@ -1332,51 +1433,14 @@ setup_nodejs() {
1332
1433
  fi
1333
1434
  ;;
1334
1435
  apt)
1436
+ local install_node
1335
1437
  read -r -p "Install Node.js via apt? [Y/n]: " install_node
1336
1438
  if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1337
- # Clean up stale Tabby packagecloud repo if present (causes apt-get update failures)
1338
- if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
1339
- local arch
1340
- arch=$(uname -m)
1341
- if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
1342
- print_info "Removing stale Tabby repo (not available for ARM64)..."
1343
- sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
1344
- sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
1345
- fi
1346
- fi
1347
- # Use NodeSource for a recent version (apt default may be old)
1348
- print_info "Installing Node.js (via NodeSource for latest LTS)..."
1349
- if command -v curl >/dev/null 2>&1; then
1350
- # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
1351
- VERIFIED_INSTALL_SUDO="true"
1352
- if verified_install "NodeSource repository" "https://deb.nodesource.com/setup_22.x"; then
1353
- # Install nodejs (NodeSource bundles npm, but distro fallback may not)
1354
- # Include npm explicitly in case NodeSource setup failed silently
1355
- # and apt falls back to the distro nodejs package (which lacks npm)
1356
- if sudo apt-get install -y nodejs npm 2>/dev/null || sudo apt-get install -y nodejs; then
1357
- print_success "Node.js installed: $(node --version)"
1358
- else
1359
- print_warning "Node.js installation failed"
1360
- fi
1361
- else
1362
- # Fallback to distro package
1363
- print_info "Falling back to distro Node.js package..."
1364
- if sudo apt-get install -y nodejs npm; then
1365
- print_success "Node.js installed: $(node --version)"
1366
- else
1367
- print_warning "Node.js installation failed"
1368
- fi
1369
- fi
1370
- else
1371
- if sudo apt-get install -y nodejs npm; then
1372
- print_success "Node.js installed: $(node --version)"
1373
- else
1374
- print_warning "Node.js installation failed"
1375
- fi
1376
- fi
1439
+ _install_nodejs_apt
1377
1440
  fi
1378
1441
  ;;
1379
1442
  dnf | yum)
1443
+ local install_node
1380
1444
  read -r -p "Install Node.js via $pkg_manager? [Y/n]: " install_node
1381
1445
  if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1382
1446
  if sudo "$pkg_manager" install -y nodejs npm; then
@@ -1387,6 +1451,7 @@ setup_nodejs() {
1387
1451
  fi
1388
1452
  ;;
1389
1453
  pacman)
1454
+ local install_node
1390
1455
  read -r -p "Install Node.js via pacman? [Y/n]: " install_node
1391
1456
  if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1392
1457
  if sudo pacman -S --noconfirm nodejs npm; then
@@ -1397,6 +1462,7 @@ setup_nodejs() {
1397
1462
  fi
1398
1463
  ;;
1399
1464
  apk)
1465
+ local install_node
1400
1466
  read -r -p "Install Node.js via apk? [Y/n]: " install_node
1401
1467
  if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1402
1468
  if sudo apk add nodejs npm; then