aidevops 2.172.17 → 2.172.19

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.
@@ -0,0 +1,1373 @@
1
+ #!/usr/bin/env bash
2
+ # Tool installation functions: git-clis, fd, ripgrep, shellcheck, shfmt, rosetta, worktrunk, minisim, recommended-tools, nodejs, python, orbstack
3
+ # Part of aidevops setup.sh modularization (t316.3)
4
+
5
+ # Shell safety baseline
6
+ set -Eeuo pipefail
7
+ IFS=$'\n\t'
8
+ # shellcheck disable=SC2154 # rc is assigned by $? in the trap string
9
+ trap 'rc=$?; echo "[ERROR] ${BASH_SOURCE[0]}:${LINENO} exit $rc" >&2' ERR
10
+ shopt -s inherit_errexit 2>/dev/null || true
11
+
12
+ setup_git_clis() {
13
+ print_info "Setting up Git CLI tools..."
14
+
15
+ local cli_tools=()
16
+ local missing_packages=()
17
+ local missing_names=()
18
+
19
+ # Check for GitHub CLI
20
+ if ! command -v gh >/dev/null 2>&1; then
21
+ missing_packages+=("gh")
22
+ missing_names+=("GitHub CLI")
23
+ else
24
+ cli_tools+=("GitHub CLI")
25
+ fi
26
+
27
+ # Check for GitLab CLI
28
+ if ! command -v glab >/dev/null 2>&1; then
29
+ missing_packages+=("glab")
30
+ missing_names+=("GitLab CLI")
31
+ else
32
+ cli_tools+=("GitLab CLI")
33
+ fi
34
+
35
+ # Report found tools
36
+ if [[ ${#cli_tools[@]} -gt 0 ]]; then
37
+ print_success "Found Git CLI tools: ${cli_tools[*]}"
38
+ fi
39
+
40
+ # Offer to install missing tools
41
+ if [[ ${#missing_packages[@]} -gt 0 ]]; then
42
+ print_warning "Missing Git CLI tools: ${missing_names[*]}"
43
+ echo " These provide enhanced Git platform integration (repos, PRs, issues)"
44
+
45
+ local pkg_manager
46
+ pkg_manager=$(detect_package_manager)
47
+
48
+ if [[ "$pkg_manager" != "unknown" ]]; then
49
+ echo ""
50
+ read -r -p "Install Git CLI tools (${missing_packages[*]}) using $pkg_manager? [Y/n]: " install_git_clis
51
+
52
+ if [[ "$install_git_clis" =~ ^[Yy]?$ ]]; then
53
+ print_info "Installing ${missing_packages[*]}..."
54
+ if install_packages "$pkg_manager" "${missing_packages[@]}"; then
55
+ print_success "Git CLI tools installed"
56
+ echo ""
57
+ echo "📋 Next steps - authenticate each CLI:"
58
+ for pkg in "${missing_packages[@]}"; do
59
+ case "$pkg" in
60
+ gh) echo " • gh auth login" ;;
61
+ glab) echo " • glab auth login" ;;
62
+ esac
63
+ done
64
+ else
65
+ print_warning "Failed to install some Git CLI tools (non-critical)"
66
+ fi
67
+ else
68
+ print_info "Skipped Git CLI tools installation"
69
+ echo ""
70
+ echo "📋 Manual installation:"
71
+ echo " macOS: brew install ${missing_packages[*]}"
72
+ echo " Ubuntu: sudo apt install ${missing_packages[*]}"
73
+ echo " Fedora: sudo dnf install ${missing_packages[*]}"
74
+ fi
75
+ else
76
+ echo ""
77
+ echo "📋 Manual installation:"
78
+ echo " macOS: brew install ${missing_packages[*]}"
79
+ echo " Ubuntu: sudo apt install ${missing_packages[*]}"
80
+ echo " Fedora: sudo dnf install ${missing_packages[*]}"
81
+ fi
82
+ else
83
+ print_success "All Git CLI tools installed and ready!"
84
+ fi
85
+
86
+ # Check for Gitea CLI separately (not in standard package managers)
87
+ if ! command -v tea >/dev/null 2>&1; then
88
+ print_info "Gitea CLI (tea) not found - install manually if needed:"
89
+ echo " go install code.gitea.io/tea/cmd/tea@latest"
90
+ echo " Or download from: https://dl.gitea.io/tea/"
91
+ else
92
+ print_success "Gitea CLI (tea) found"
93
+ fi
94
+
95
+ return 0
96
+ }
97
+
98
+ setup_file_discovery_tools() {
99
+ print_info "Setting up file discovery tools..."
100
+
101
+ local missing_tools=()
102
+ local missing_packages=()
103
+ local missing_names=()
104
+
105
+ local fd_version
106
+ if command -v fd >/dev/null 2>&1; then
107
+ fd_version=$(fd --version 2>/dev/null | head -1 || echo "unknown")
108
+ print_success "fd found: $fd_version"
109
+ elif command -v fdfind >/dev/null 2>&1; then
110
+ fd_version=$(fdfind --version 2>/dev/null | head -1 || echo "unknown")
111
+ print_success "fd found (as fdfind): $fd_version"
112
+ print_warning "Note: 'fd' alias not active in current shell. Restart shell or run: alias fd=fdfind"
113
+ else
114
+ missing_tools+=("fd")
115
+ missing_packages+=("fd")
116
+ missing_names+=("fd (fast file finder)")
117
+ fi
118
+
119
+ # Check for ripgrep
120
+ if ! command -v rg >/dev/null 2>&1; then
121
+ missing_tools+=("rg")
122
+ missing_packages+=("ripgrep")
123
+ missing_names+=("ripgrep (fast content search)")
124
+ else
125
+ local rg_version
126
+ rg_version=$(rg --version 2>/dev/null | head -1 || echo "unknown")
127
+ print_success "ripgrep found: $rg_version"
128
+ fi
129
+
130
+ # Check for ripgrep-all (searches inside PDFs, DOCX, SQLite, archives)
131
+ if ! command -v rga >/dev/null 2>&1; then
132
+ missing_tools+=("rga")
133
+ missing_packages+=("ripgrep-all")
134
+ missing_names+=("ripgrep-all (search inside PDFs/docs/archives)")
135
+ else
136
+ local rga_version
137
+ rga_version=$(rga --version 2>/dev/null | head -1 || echo "unknown")
138
+ print_success "ripgrep-all found: $rga_version"
139
+ fi
140
+
141
+ # Offer to install missing tools
142
+ if [[ ${#missing_tools[@]} -gt 0 ]]; then
143
+ print_warning "Missing file discovery tools: ${missing_names[*]}"
144
+ echo ""
145
+ echo " These tools provide 10x faster file discovery than built-in glob:"
146
+ echo " fd - Fast alternative to 'find', respects .gitignore"
147
+ echo " ripgrep - Fast alternative to 'grep', respects .gitignore"
148
+ echo " ripgrep-all - Extends ripgrep to search inside PDFs, DOCX, SQLite, archives"
149
+ echo ""
150
+ echo " AI agents use these for efficient codebase navigation."
151
+ echo ""
152
+
153
+ local pkg_manager
154
+ pkg_manager=$(detect_package_manager)
155
+
156
+ if [[ "$pkg_manager" != "unknown" ]]; then
157
+ local install_fd_tools="y"
158
+ if [[ "$INTERACTIVE_MODE" == "true" ]]; then
159
+ read -r -p "Install file discovery tools (${missing_packages[*]}) using $pkg_manager? [Y/n]: " install_fd_tools
160
+ fi
161
+
162
+ 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
213
+ else
214
+ 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"
221
+ fi
222
+ 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"
229
+ fi
230
+ else
231
+ print_success "All file discovery tools installed!"
232
+ fi
233
+
234
+ return 0
235
+ }
236
+
237
+ setup_rtk() {
238
+ # rtk — CLI proxy that reduces LLM token consumption by 60-90% (t1430)
239
+ # Optional optimization: compresses git/gh/test outputs before they reach LLM context
240
+ # Single Rust binary, zero dependencies, <10ms overhead
241
+ # https://github.com/rtk-ai/rtk
242
+
243
+ # Pin to a tagged release for stability and auditability (Gemini review feedback).
244
+ # Update the tag when upstream-watch detects a new release.
245
+ local rtk_installer_url="https://raw.githubusercontent.com/rtk-ai/rtk/v0.28.2/install.sh"
246
+
247
+ if command -v rtk >/dev/null 2>&1; then
248
+ local rtk_version
249
+ rtk_version=$(rtk --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
250
+ print_success "rtk found: v$rtk_version (token optimization proxy)"
251
+ # Fall through to ensure config is applied (telemetry, tee)
252
+ else
253
+ print_info "rtk (Rust Token Killer) reduces LLM token usage by 60-90% on CLI commands"
254
+ echo " Compresses git, gh, test runner, and linter outputs before they reach the AI context."
255
+ echo " Single binary, zero dependencies, <10ms overhead."
256
+ echo ""
257
+
258
+ local install_rtk="n"
259
+ if [[ "$INTERACTIVE_MODE" == "true" ]]; then
260
+ read -r -p "Install rtk for token-optimized CLI output? [y/N]: " install_rtk
261
+ fi
262
+
263
+ if [[ "$install_rtk" =~ ^[Yy]$ ]]; then
264
+ VERIFIED_INSTALL_SHELL="sh"
265
+ if command -v brew >/dev/null 2>&1; then
266
+ if run_with_spinner "Installing rtk via Homebrew" brew install rtk; then
267
+ print_success "rtk installed via Homebrew"
268
+ else
269
+ print_warning "Homebrew install failed, trying curl installer..."
270
+ if verified_install "rtk" "$rtk_installer_url"; then
271
+ print_success "rtk installed to ~/.local/bin/rtk"
272
+ else
273
+ print_warning "rtk installation failed (non-critical, optional tool)"
274
+ fi
275
+ fi
276
+ else
277
+ # Linux or macOS without brew — use verified_install for secure execution
278
+ if verified_install "rtk" "$rtk_installer_url"; then
279
+ print_success "rtk installed to ~/.local/bin/rtk"
280
+ else
281
+ print_warning "rtk installation failed (non-critical, optional tool)"
282
+ echo " Manual install: https://github.com/rtk-ai/rtk#installation"
283
+ fi
284
+ fi
285
+ else
286
+ print_info "Skipped rtk installation (optional)"
287
+ echo " Manual install: brew install rtk OR curl -fsSL $rtk_installer_url | sh"
288
+ fi
289
+ fi
290
+
291
+ # Configure rtk (telemetry off, tee for failure capture) — only if binary is present
292
+ if command -v rtk >/dev/null 2>&1; then
293
+ local rtk_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/rtk"
294
+ if [[ ! -f "$rtk_config_dir/config.toml" ]]; then
295
+ mkdir -p "$rtk_config_dir"
296
+ cat >"$rtk_config_dir/config.toml" <<-'RTKEOF'
297
+ # rtk configuration (created by aidevops setup.sh)
298
+ # https://github.com/rtk-ai/rtk
299
+
300
+ [telemetry]
301
+ enabled = false
302
+
303
+ [tee]
304
+ enabled = true
305
+ mode = "failures"
306
+ max_files = 20
307
+ RTKEOF
308
+ print_success "rtk config created (telemetry disabled)"
309
+ fi
310
+ fi
311
+
312
+ return 0
313
+ }
314
+
315
+ setup_shell_linting_tools() {
316
+ print_info "Setting up shell linting tools..."
317
+
318
+ local missing_tools=()
319
+ local pkg_manager
320
+ pkg_manager=$(detect_package_manager)
321
+
322
+ # Check shellcheck
323
+ if command -v shellcheck >/dev/null 2>&1; then
324
+ local sc_version sc_rosetta=false
325
+ sc_version=$(shellcheck --version 2>/dev/null | grep 'version:' | awk '{print $2}' || echo "unknown")
326
+ # Rosetta detection (macOS Apple Silicon only, requires `file` command)
327
+ if [[ "$PLATFORM_MACOS" == "true" ]] && [[ "$PLATFORM_ARM64" == "true" ]] && command -v file >/dev/null 2>&1; then
328
+ local sc_file_output
329
+ sc_file_output=$(file "$(command -v shellcheck)" 2>/dev/null || echo "")
330
+ if [[ "$sc_file_output" == *"x86_64"* ]] && [[ "$sc_file_output" != *"arm64"* ]]; then
331
+ sc_rosetta=true
332
+ fi
333
+ fi
334
+ if [[ "$sc_rosetta" == "true" ]]; then
335
+ print_warning "shellcheck found but running under Rosetta (x86_64)"
336
+ print_info " Run 'rosetta-audit-helper.sh migrate' to fix"
337
+ else
338
+ print_success "shellcheck found ($sc_version)"
339
+ fi
340
+ else
341
+ missing_tools+=("shellcheck")
342
+ fi
343
+
344
+ # Check shfmt
345
+ if command -v shfmt >/dev/null 2>&1; then
346
+ print_success "shfmt found ($(shfmt --version 2>/dev/null))"
347
+ else
348
+ missing_tools+=("shfmt")
349
+ fi
350
+
351
+ if [[ ${#missing_tools[@]} -gt 0 ]]; then
352
+ print_warning "Missing shell linting tools: ${missing_tools[*]}"
353
+ echo " shellcheck - static analysis for shell scripts"
354
+ echo " shfmt - shell script formatter (fast syntax checks)"
355
+
356
+ if [[ "$pkg_manager" != "unknown" ]]; then
357
+ local install_linters
358
+ if [[ "${NON_INTERACTIVE:-}" == "true" ]]; then
359
+ install_linters="Y"
360
+ else
361
+ read -r -p "Install missing shell linting tools using $pkg_manager? [Y/n]: " install_linters
362
+ fi
363
+
364
+ if [[ "$install_linters" =~ ^[Yy]?$ ]]; then
365
+ if install_packages "$pkg_manager" "${missing_tools[@]}"; then
366
+ print_success "Shell linting tools installed"
367
+ else
368
+ print_warning "Failed to install some shell linting tools"
369
+ fi
370
+ else
371
+ print_info "Skipped shell linting tools"
372
+ fi
373
+ else
374
+ echo " Install manually:"
375
+ echo " macOS: brew install ${missing_tools[*]}"
376
+ echo " Linux: apt install ${missing_tools[*]}"
377
+ fi
378
+ fi
379
+
380
+ return 0
381
+ }
382
+
383
+ setup_shellcheck_wrapper() {
384
+ # Replace the real shellcheck binary with our wrapper script to prevent
385
+ # --external-sources from causing exponential memory growth (GH#2915).
386
+ # This intercepts ALL callers including compiled binaries (e.g., OpenCode)
387
+ # that invoke shellcheck by absolute path rather than via PATH.
388
+
389
+ local wrapper_src="${INSTALL_DIR:-.}/.agents/scripts/shellcheck-wrapper.sh"
390
+ if [[ ! -f "$wrapper_src" ]]; then
391
+ print_info "shellcheck-wrapper.sh not found — skipping binary replacement"
392
+ return 0
393
+ fi
394
+
395
+ # Find the real shellcheck binary
396
+ local sc_path
397
+ sc_path="$(command -v shellcheck 2>/dev/null || true)"
398
+ if [[ -z "$sc_path" ]]; then
399
+ print_info "shellcheck not installed — wrapper not needed yet"
400
+ return 0
401
+ fi
402
+
403
+ # Resolve symlinks to get the actual binary path
404
+ local sc_resolved
405
+ sc_resolved="$(realpath "$sc_path" 2>/dev/null || readlink -f "$sc_path" 2>/dev/null || echo "$sc_path")"
406
+
407
+ # Check if the binary is already our wrapper (idempotent)
408
+ if head -5 "$sc_resolved" 2>/dev/null | grep -q "shellcheck-wrapper" 2>/dev/null; then
409
+ # Already replaced — check that .real exists
410
+ local real_path="${sc_resolved}.real"
411
+ if [[ ! -x "$real_path" ]]; then
412
+ print_warning "shellcheck wrapper installed but .real binary missing at $real_path"
413
+ print_info "Reinstall shellcheck (brew reinstall shellcheck) then re-run setup"
414
+ return 0
415
+ fi
416
+
417
+ # Check if the installed wrapper is outdated vs the source
418
+ if ! diff -q "$wrapper_src" "$sc_resolved" >/dev/null 2>&1; then
419
+ print_info "Updating shellcheck wrapper at $sc_resolved (source is newer)"
420
+ if cp "$wrapper_src" "$sc_resolved" 2>/dev/null || sudo cp "$wrapper_src" "$sc_resolved" 2>/dev/null; then
421
+ chmod +x "$sc_resolved" 2>/dev/null || sudo chmod +x "$sc_resolved" 2>/dev/null || true
422
+ print_success "shellcheck wrapper updated at $sc_resolved"
423
+ else
424
+ print_warning "Cannot update wrapper — insufficient permissions"
425
+ fi
426
+ else
427
+ print_success "shellcheck wrapper already installed at $sc_resolved"
428
+ fi
429
+ return 0
430
+ fi
431
+
432
+ # The binary at sc_resolved is the real shellcheck — replace it
433
+ local real_dest="${sc_resolved}.real"
434
+
435
+ print_info "Installing shellcheck wrapper at $sc_resolved"
436
+ print_info " Real binary will be moved to $real_dest"
437
+
438
+ # Move real binary to .real suffix
439
+ if ! mv "$sc_resolved" "$real_dest" 2>/dev/null; then
440
+ # May need sudo (e.g., /usr/local/bin on some systems)
441
+ if ! sudo mv "$sc_resolved" "$real_dest" 2>/dev/null; then
442
+ print_warning "Cannot move shellcheck binary — insufficient permissions"
443
+ print_info "Run manually: sudo mv '$sc_resolved' '$real_dest'"
444
+ return 0
445
+ fi
446
+ fi
447
+
448
+ # Copy wrapper to the original path
449
+ if ! cp "$wrapper_src" "$sc_resolved" 2>/dev/null; then
450
+ if ! sudo cp "$wrapper_src" "$sc_resolved" 2>/dev/null; then
451
+ # Rollback
452
+ mv "$real_dest" "$sc_resolved" 2>/dev/null || sudo mv "$real_dest" "$sc_resolved" 2>/dev/null || true
453
+ print_warning "Cannot install wrapper — insufficient permissions"
454
+ return 0
455
+ fi
456
+ fi
457
+
458
+ # Ensure wrapper is executable
459
+ chmod +x "$sc_resolved" 2>/dev/null || sudo chmod +x "$sc_resolved" 2>/dev/null || true
460
+
461
+ print_success "shellcheck wrapper installed — --external-sources will be stripped"
462
+ print_info " Real binary: $real_dest"
463
+ print_info " Wrapper: $sc_resolved"
464
+
465
+ return 0
466
+ }
467
+
468
+ setup_qlty_cli() {
469
+ print_info "Setting up Qlty CLI (multi-linter code quality)..."
470
+
471
+ local qlty_bin="${HOME}/.qlty/bin/qlty"
472
+
473
+ # Check if already installed
474
+ if [[ -x "$qlty_bin" ]]; then
475
+ local qlty_version
476
+ qlty_version=$("$qlty_bin" --version 2>/dev/null | head -1 || echo "unknown")
477
+ print_success "Qlty CLI already installed: $qlty_version"
478
+ return 0
479
+ fi
480
+
481
+ # Also check PATH in case it's installed elsewhere
482
+ if command -v qlty >/dev/null 2>&1; then
483
+ local qlty_version
484
+ qlty_version=$(qlty --version 2>/dev/null | head -1 || echo "unknown")
485
+ print_success "Qlty CLI found in PATH: $qlty_version"
486
+ return 0
487
+ fi
488
+
489
+ print_info "Qlty provides universal code quality analysis for 40+ languages"
490
+ echo " - Runs 70+ static analysis tools (ShellCheck, ESLint, etc.)"
491
+ echo " - Detects code smells and maintainability issues"
492
+ echo " - Used by the daily code quality sweep (pulse-wrapper.sh)"
493
+ echo ""
494
+
495
+ local install_qlty="Y"
496
+ if [[ "${NON_INTERACTIVE:-}" != "true" ]]; then
497
+ read -r -p "Install Qlty CLI? [Y/n]: " install_qlty
498
+ fi
499
+
500
+ if [[ "$install_qlty" =~ ^[Yy]?$ ]]; then
501
+ if command -v curl >/dev/null 2>&1; then
502
+ if verified_install "Qlty CLI" "https://qlty.sh"; then
503
+ # Verify installation
504
+ if [[ -x "$qlty_bin" ]]; then
505
+ local qlty_version
506
+ qlty_version=$("$qlty_bin" --version 2>/dev/null | head -1 || echo "unknown")
507
+ print_success "Qlty CLI installed: $qlty_version"
508
+ print_info "Ensure ~/.qlty/bin is in your PATH"
509
+ print_info "Documentation: ~/.aidevops/agents/tools/code-review/qlty.md"
510
+ elif command -v qlty >/dev/null 2>&1; then
511
+ print_success "Qlty CLI installed: $(qlty --version 2>/dev/null | head -1)"
512
+ else
513
+ print_warning "Qlty CLI install script ran but binary not found at $qlty_bin"
514
+ print_info "Try restarting your shell or check ~/.qlty/bin/"
515
+ fi
516
+ else
517
+ print_warning "Qlty CLI installation failed"
518
+ print_info "Install manually: curl -fsSL https://qlty.sh | bash"
519
+ fi
520
+ else
521
+ print_warning "curl not found — cannot install Qlty CLI"
522
+ print_info "Install manually: curl -fsSL https://qlty.sh | bash"
523
+ fi
524
+ else
525
+ print_info "Skipped Qlty CLI installation"
526
+ print_info "Install later: curl -fsSL https://qlty.sh | bash"
527
+ fi
528
+
529
+ return 0
530
+ }
531
+
532
+ setup_rosetta_audit() {
533
+ # Skip on non-Apple-Silicon or non-macOS
534
+ if [[ "$(uname)" != "Darwin" ]] || [[ "$(uname -m)" != "arm64" ]]; then
535
+ print_info "Rosetta audit: not applicable (Intel Mac or non-macOS)"
536
+ return 0
537
+ fi
538
+
539
+ # Skip if no dual-brew setup
540
+ if [[ ! -x "/usr/local/bin/brew" ]] || [[ ! -x "/opt/homebrew/bin/brew" ]]; then
541
+ print_success "Rosetta audit: clean Homebrew setup (no x86 brew detected)"
542
+ return 0
543
+ fi
544
+
545
+ print_info "Detected dual Homebrew (x86 + ARM) — checking for Rosetta overhead..."
546
+
547
+ local x86_only_count dup_count
548
+ dup_count=$(comm -12 \
549
+ <(/usr/local/bin/brew list --formula 2>/dev/null | sort) \
550
+ <(/opt/homebrew/bin/brew list --formula 2>/dev/null | sort) | wc -l | tr -d ' ')
551
+ x86_only_count=$(comm -23 \
552
+ <(/usr/local/bin/brew list --formula 2>/dev/null | sort) \
553
+ <(/opt/homebrew/bin/brew list --formula 2>/dev/null | sort) | wc -l | tr -d ' ')
554
+
555
+ local total=$((x86_only_count + dup_count))
556
+
557
+ if [[ "$total" -eq 0 ]]; then
558
+ print_success "No x86 Homebrew packages found — clean ARM setup"
559
+ return 0
560
+ fi
561
+
562
+ print_warning "Found $total x86 Homebrew packages ($x86_only_count x86-only, $dup_count duplicates)"
563
+ echo " These run under Rosetta 2 emulation with ~30% performance overhead"
564
+ echo ""
565
+ echo " To audit: rosetta-audit-helper.sh scan"
566
+ echo " To migrate: rosetta-audit-helper.sh migrate --dry-run"
567
+ echo " To fix: rosetta-audit-helper.sh migrate"
568
+
569
+ return 0
570
+ }
571
+
572
+ setup_worktrunk() {
573
+ print_info "Setting up Worktrunk (git worktree management)..."
574
+
575
+ # Check if worktrunk (wt) is already installed
576
+ if command -v wt >/dev/null 2>&1; then
577
+ local wt_version
578
+ wt_version=$(wt --version 2>/dev/null | head -1 || echo "unknown")
579
+ 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
607
+ return 0
608
+ fi
609
+
610
+ # Worktrunk not installed - offer to install
611
+ print_info "Worktrunk makes git worktrees as easy as branches"
612
+ echo " • wt switch feat - Switch/create worktree (with cd)"
613
+ echo " • wt list - List worktrees with CI status"
614
+ echo " • wt merge - Squash/rebase/merge + cleanup"
615
+ echo " • Hooks for automated setup (npm install, etc.)"
616
+ echo ""
617
+ echo " Note: aidevops also includes worktree-helper.sh as a fallback"
618
+ echo ""
619
+
620
+ local pkg_manager
621
+ pkg_manager=$(detect_package_manager)
622
+
623
+ 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
653
+ 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
672
+ else
673
+ print_warning "Worktrunk not installed"
674
+ echo ""
675
+ echo " Install options:"
676
+ echo " macOS/Linux (Homebrew): brew install max-sixty/worktrunk/wt"
677
+ echo " Cargo: cargo install worktrunk"
678
+ echo " Windows: winget install max-sixty.worktrunk"
679
+ echo ""
680
+ echo " After install: wt config shell install"
681
+ echo ""
682
+ print_info "Fallback available: ~/.aidevops/agents/scripts/worktree-helper.sh"
683
+ fi
684
+
685
+ return 0
686
+ }
687
+
688
+ setup_recommended_tools() {
689
+ print_info "Checking recommended development tools..."
690
+
691
+ local missing_tools=()
692
+ local missing_names=()
693
+
694
+ # Check for Tabby terminal
695
+ if [[ "$(uname)" == "Darwin" ]]; then
696
+ # macOS - check Applications folder
697
+ if [[ ! -d "/Applications/Tabby.app" ]]; then
698
+ missing_tools+=("tabby")
699
+ missing_names+=("Tabby (modern terminal)")
700
+ else
701
+ print_success "Tabby terminal found"
702
+ fi
703
+ elif [[ "$(uname)" == "Linux" ]]; then
704
+ # Linux - check if tabby command exists
705
+ if ! command -v tabby >/dev/null 2>&1; then
706
+ missing_tools+=("tabby")
707
+ missing_names+=("Tabby (modern terminal)")
708
+ else
709
+ print_success "Tabby terminal found"
710
+ fi
711
+ fi
712
+
713
+ # Check for Zed editor
714
+ local zed_exists=false
715
+ if [[ "$(uname)" == "Darwin" ]]; then
716
+ # macOS - check Applications folder
717
+ if [[ ! -d "/Applications/Zed.app" ]]; then
718
+ missing_tools+=("zed")
719
+ missing_names+=("Zed (AI-native editor)")
720
+ else
721
+ print_success "Zed editor found"
722
+ zed_exists=true
723
+ fi
724
+ elif [[ "$(uname)" == "Linux" ]]; then
725
+ # Linux - check if zed command exists
726
+ if ! command -v zed >/dev/null 2>&1; then
727
+ missing_tools+=("zed")
728
+ missing_names+=("Zed (AI-native editor)")
729
+ else
730
+ print_success "Zed editor found"
731
+ zed_exists=true
732
+ fi
733
+ fi
734
+
735
+ # Check for OpenCode extension in existing Zed installation
736
+ 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
762
+ fi
763
+
764
+ # Offer to install missing tools
765
+ if [[ ${#missing_tools[@]} -gt 0 ]]; then
766
+ print_warning "Missing recommended tools: ${missing_names[*]}"
767
+ echo " Tabby - Modern terminal with profiles, SSH manager, split panes"
768
+ echo " Zed - High-performance AI-native code editor"
769
+ echo ""
770
+
771
+ # Install Tabby if missing
772
+ 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
844
+ fi
845
+
846
+ # Install Zed if missing
847
+ 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
894
+ fi
895
+ else
896
+ print_success "All recommended tools installed!"
897
+ fi
898
+
899
+ return 0
900
+ }
901
+
902
+ setup_minisim() {
903
+ # Only available on macOS
904
+ if [[ "$(uname)" != "Darwin" ]]; then
905
+ return 0
906
+ fi
907
+
908
+ print_info "Setting up MiniSim (iOS/Android emulator launcher)..."
909
+
910
+ # Check if MiniSim is already installed
911
+ if [[ -d "/Applications/MiniSim.app" ]]; then
912
+ print_success "MiniSim already installed"
913
+ print_info "Global shortcut: Option + Shift + E"
914
+ return 0
915
+ fi
916
+
917
+ # Check if Xcode or Android Studio is installed (MiniSim needs at least one)
918
+ local has_xcode=false
919
+ local has_android=false
920
+
921
+ if command -v xcrun >/dev/null 2>&1 && xcrun simctl list devices >/dev/null 2>&1; then
922
+ has_xcode=true
923
+ fi
924
+
925
+ if [[ -n "${ANDROID_HOME:-}" ]] || [[ -n "${ANDROID_SDK_ROOT:-}" ]] || [[ -d "$HOME/Library/Android/sdk" ]]; then
926
+ has_android=true
927
+ fi
928
+
929
+ if [[ "$has_xcode" == "false" && "$has_android" == "false" ]]; then
930
+ print_info "MiniSim requires Xcode (iOS) or Android Studio (Android)"
931
+ print_info "Install one of these first, then re-run setup to install MiniSim"
932
+ return 0
933
+ fi
934
+
935
+ # Show what's available
936
+ local available_for=""
937
+ if [[ "$has_xcode" == "true" ]]; then
938
+ available_for="iOS simulators"
939
+ fi
940
+ if [[ "$has_android" == "true" ]]; then
941
+ if [[ -n "$available_for" ]]; then
942
+ available_for="$available_for and Android emulators"
943
+ else
944
+ available_for="Android emulators"
945
+ fi
946
+ fi
947
+
948
+ print_info "MiniSim is a menu bar app for launching $available_for"
949
+ echo " Features:"
950
+ echo " - Global shortcut: Option + Shift + E"
951
+ echo " - Launch/manage iOS simulators and Android emulators"
952
+ echo " - Copy device UDID/ADB ID"
953
+ echo " - Cold boot Android emulators"
954
+ echo " - Run Android emulators without audio (saves Bluetooth battery)"
955
+ echo ""
956
+
957
+ # Check if Homebrew is available
958
+ if ! command -v brew >/dev/null 2>&1; then
959
+ print_warning "Homebrew not found - cannot install MiniSim automatically"
960
+ echo " Install manually: https://github.com/okwasniewski/MiniSim/releases"
961
+ return 0
962
+ fi
963
+
964
+ local install_minisim
965
+ read -r -p "Install MiniSim? [Y/n]: " install_minisim
966
+
967
+ if [[ "$install_minisim" =~ ^[Yy]?$ ]]; then
968
+ if run_with_spinner "Installing MiniSim" brew install --cask minisim; then
969
+ print_info "Global shortcut: Option + Shift + E"
970
+ print_info "Documentation: ~/.aidevops/agents/tools/mobile/minisim.md"
971
+ else
972
+ print_warning "Failed to install MiniSim via Homebrew"
973
+ echo " Install manually: https://github.com/okwasniewski/MiniSim/releases"
974
+ fi
975
+ else
976
+ print_info "Skipped MiniSim installation"
977
+ print_info "Install later: brew install --cask minisim"
978
+ fi
979
+
980
+ return 0
981
+ }
982
+
983
+ setup_ssh_key() {
984
+ print_info "Checking SSH key setup..."
985
+
986
+ if [[ ! -f ~/.ssh/id_ed25519 ]]; then
987
+ print_warning "Ed25519 SSH key not found"
988
+ read -r -p "Generate new Ed25519 SSH key? [Y/n]: " generate_key
989
+
990
+ if [[ "$generate_key" =~ ^[Yy]?$ ]]; then
991
+ read -r -p "Enter your email address: " email
992
+ mkdir -p ~/.ssh && chmod 700 ~/.ssh
993
+ ssh-keygen -t ed25519 -C "$email" -f ~/.ssh/id_ed25519
994
+ print_success "SSH key generated"
995
+ else
996
+ print_info "Skipping SSH key generation"
997
+ fi
998
+ else
999
+ print_success "Ed25519 SSH key found"
1000
+ fi
1001
+ return 0
1002
+ }
1003
+
1004
+ setup_python_env() {
1005
+ print_info "Setting up Python environment for DSPy..."
1006
+
1007
+ # Check if Python 3 is available
1008
+ local python3_bin
1009
+ if ! python3_bin=$(find_python3); then
1010
+ print_warning "Python 3 not found - DSPy setup skipped"
1011
+ print_info "Install Python 3.8+ to enable DSPy integration"
1012
+ return
1013
+ fi
1014
+
1015
+ local python_version
1016
+ python_version=$("$python3_bin" --version | cut -d' ' -f2 | cut -d'.' -f1-2)
1017
+ local version_check
1018
+ version_check=$("$python3_bin" -c "import sys; print(1 if sys.version_info >= (3, 8) else 0)")
1019
+
1020
+ if [[ "$version_check" != "1" ]]; then
1021
+ print_warning "Python 3.8+ required for DSPy, found $python_version - DSPy setup skipped"
1022
+ return
1023
+ fi
1024
+
1025
+ # Create Python virtual environment
1026
+ if [[ ! -d "python-env/dspy-env" ]] || [[ ! -f "python-env/dspy-env/bin/activate" ]]; then
1027
+ print_info "Creating Python virtual environment for DSPy..."
1028
+ mkdir -p python-env
1029
+ # Remove corrupted venv if directory exists but activate script is missing
1030
+ if [[ -d "python-env/dspy-env" ]] && [[ ! -f "python-env/dspy-env/bin/activate" ]]; then
1031
+ rm -rf python-env/dspy-env
1032
+ fi
1033
+ if python3 -m venv python-env/dspy-env; then
1034
+ print_success "Python virtual environment created"
1035
+ else
1036
+ print_warning "Failed to create Python virtual environment - DSPy setup skipped"
1037
+ return
1038
+ fi
1039
+ else
1040
+ print_info "Python virtual environment already exists"
1041
+ fi
1042
+
1043
+ # Install DSPy dependencies
1044
+ print_info "Installing DSPy dependencies..."
1045
+ # shellcheck source=/dev/null
1046
+ if [[ -f "python-env/dspy-env/bin/activate" ]]; then
1047
+ source python-env/dspy-env/bin/activate
1048
+ else
1049
+ print_warning "Python venv activate script not found - DSPy setup skipped"
1050
+ return
1051
+ fi
1052
+ pip install --upgrade pip >/dev/null 2>&1
1053
+
1054
+ if run_with_spinner "Installing DSPy dependencies" pip install -r requirements.txt; then
1055
+ : # Success message handled by spinner
1056
+ else
1057
+ print_info "Check requirements.txt or run manually:"
1058
+ print_info " source python-env/dspy-env/bin/activate && pip install -r requirements.txt"
1059
+ fi
1060
+ }
1061
+
1062
+ setup_nodejs_env() {
1063
+ print_info "Setting up Node.js environment for DSPyGround..."
1064
+
1065
+ # Check if Node.js is available
1066
+ if ! command -v node &>/dev/null; then
1067
+ print_warning "Node.js not found - DSPyGround setup skipped"
1068
+ print_info "Install Node.js 18+ to enable DSPyGround integration"
1069
+ return
1070
+ fi
1071
+
1072
+ local node_version
1073
+ node_version=$(node --version 2>/dev/null | cut -d'v' -f2 | cut -d'.' -f1)
1074
+ if [[ -z "$node_version" ]] || ! [[ "$node_version" =~ ^[0-9]+$ ]]; then
1075
+ print_warning "Could not determine Node.js version - DSPyGround setup skipped"
1076
+ return
1077
+ fi
1078
+ if [[ "$node_version" -lt 18 ]]; then
1079
+ print_warning "Node.js 18+ required for DSPyGround, found v$node_version - DSPyGround setup skipped"
1080
+ return
1081
+ fi
1082
+
1083
+ # Check if npm is available
1084
+ if ! command -v npm &>/dev/null; then
1085
+ print_warning "npm not found - DSPyGround setup skipped"
1086
+ return
1087
+ fi
1088
+
1089
+ # Install DSPyGround globally if not already installed
1090
+ if ! command -v dspyground &>/dev/null; then
1091
+ if run_with_spinner "Installing DSPyGround" npm_global_install dspyground; then
1092
+ : # Success message handled by spinner
1093
+ else
1094
+ print_warning "Try manually: sudo npm install -g dspyground"
1095
+ fi
1096
+ else
1097
+ print_success "DSPyGround already installed"
1098
+ fi
1099
+ }
1100
+
1101
+ setup_nodejs() {
1102
+ # Check if Node.js is already installed
1103
+ if command -v node >/dev/null 2>&1; then
1104
+ local node_version
1105
+ node_version=$(node --version 2>/dev/null || echo "unknown")
1106
+ print_success "Node.js already installed: $node_version"
1107
+ # Distro nodejs package may not include npm — install it if missing
1108
+ if ! command -v npm >/dev/null 2>&1; then
1109
+ print_info "npm not found (distro nodejs package may omit it) — installing..."
1110
+ local pkg_manager
1111
+ pkg_manager=$(detect_package_manager)
1112
+ case "$pkg_manager" in
1113
+ apt) sudo apt-get install -y npm 2>/dev/null || print_warning "Failed to install npm via apt" ;;
1114
+ dnf | yum) sudo "$pkg_manager" install -y npm 2>/dev/null || print_warning "Failed to install npm via $pkg_manager" ;;
1115
+ brew) brew install npm 2>/dev/null || print_warning "Failed to install npm via brew" ;;
1116
+ *) print_warning "Cannot auto-install npm — install manually" ;;
1117
+ esac
1118
+ fi
1119
+ return 0
1120
+ fi
1121
+
1122
+ print_info "Node.js is required for OpenCode, MCP servers, and many tools"
1123
+
1124
+ local pkg_manager
1125
+ pkg_manager=$(detect_package_manager)
1126
+
1127
+ case "$pkg_manager" in
1128
+ brew)
1129
+ read -r -p "Install Node.js via Homebrew? [Y/n]: " install_node
1130
+ if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1131
+ if run_with_spinner "Installing Node.js" brew install node; then
1132
+ print_success "Node.js installed: $(node --version)"
1133
+ else
1134
+ print_warning "Node.js installation failed"
1135
+ fi
1136
+ fi
1137
+ ;;
1138
+ apt)
1139
+ read -r -p "Install Node.js via apt? [Y/n]: " install_node
1140
+ if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1141
+ # Clean up stale Tabby packagecloud repo if present (causes apt-get update failures)
1142
+ if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
1143
+ local arch
1144
+ arch=$(uname -m)
1145
+ if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
1146
+ print_info "Removing stale Tabby repo (not available for ARM64)..."
1147
+ sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
1148
+ sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
1149
+ fi
1150
+ fi
1151
+ # Use NodeSource for a recent version (apt default may be old)
1152
+ print_info "Installing Node.js (via NodeSource for latest LTS)..."
1153
+ if command -v curl >/dev/null 2>&1; then
1154
+ # shellcheck disable=SC2034 # Read by verified_install() in setup.sh
1155
+ VERIFIED_INSTALL_SUDO="true"
1156
+ if verified_install "NodeSource repository" "https://deb.nodesource.com/setup_22.x"; then
1157
+ # Install nodejs (NodeSource bundles npm, but distro fallback may not)
1158
+ # Include npm explicitly in case NodeSource setup failed silently
1159
+ # and apt falls back to the distro nodejs package (which lacks npm)
1160
+ if sudo apt-get install -y nodejs npm 2>/dev/null || sudo apt-get install -y nodejs; then
1161
+ print_success "Node.js installed: $(node --version)"
1162
+ else
1163
+ print_warning "Node.js installation failed"
1164
+ fi
1165
+ else
1166
+ # Fallback to distro package
1167
+ print_info "Falling back to distro Node.js package..."
1168
+ if sudo apt-get install -y nodejs npm; then
1169
+ print_success "Node.js installed: $(node --version)"
1170
+ else
1171
+ print_warning "Node.js installation failed"
1172
+ fi
1173
+ fi
1174
+ else
1175
+ if sudo apt-get install -y nodejs npm; then
1176
+ print_success "Node.js installed: $(node --version)"
1177
+ else
1178
+ print_warning "Node.js installation failed"
1179
+ fi
1180
+ fi
1181
+ fi
1182
+ ;;
1183
+ dnf | yum)
1184
+ read -r -p "Install Node.js via $pkg_manager? [Y/n]: " install_node
1185
+ if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1186
+ if sudo "$pkg_manager" install -y nodejs npm; then
1187
+ print_success "Node.js installed: $(node --version)"
1188
+ else
1189
+ print_warning "Node.js installation failed"
1190
+ fi
1191
+ fi
1192
+ ;;
1193
+ pacman)
1194
+ read -r -p "Install Node.js via pacman? [Y/n]: " install_node
1195
+ if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1196
+ if sudo pacman -S --noconfirm nodejs npm; then
1197
+ print_success "Node.js installed: $(node --version)"
1198
+ else
1199
+ print_warning "Node.js installation failed"
1200
+ fi
1201
+ fi
1202
+ ;;
1203
+ apk)
1204
+ read -r -p "Install Node.js via apk? [Y/n]: " install_node
1205
+ if [[ "$install_node" =~ ^[Yy]?$ ]]; then
1206
+ if sudo apk add nodejs npm; then
1207
+ print_success "Node.js installed: $(node --version)"
1208
+ else
1209
+ print_warning "Node.js installation failed"
1210
+ fi
1211
+ fi
1212
+ ;;
1213
+ *)
1214
+ print_warning "No supported package manager found for Node.js installation"
1215
+ echo " Install manually: https://nodejs.org/"
1216
+ ;;
1217
+ esac
1218
+
1219
+ return 0
1220
+ }
1221
+
1222
+ setup_opencode_cli() {
1223
+ print_info "Setting up OpenCode CLI..."
1224
+
1225
+ # Check if OpenCode is already installed
1226
+ if command -v opencode >/dev/null 2>&1; then
1227
+ local oc_version
1228
+ oc_version=$(opencode --version 2>/dev/null | head -1 || echo "unknown")
1229
+ print_success "OpenCode already installed: $oc_version"
1230
+ return 0
1231
+ fi
1232
+
1233
+ # Need either bun or npm to install
1234
+ local installer=""
1235
+ local install_pkg="opencode-ai@latest"
1236
+
1237
+ if command -v bun >/dev/null 2>&1; then
1238
+ installer="bun"
1239
+ elif command -v npm >/dev/null 2>&1; then
1240
+ installer="npm"
1241
+ else
1242
+ print_warning "Neither bun nor npm found - cannot install OpenCode"
1243
+ print_info "Install Node.js first, then re-run setup"
1244
+ return 0
1245
+ fi
1246
+
1247
+ print_info "OpenCode is the AI coding tool that aidevops is built for"
1248
+ echo " It provides an AI-powered terminal interface for development tasks."
1249
+ echo ""
1250
+
1251
+ local install_oc="Y"
1252
+ if [[ "$NON_INTERACTIVE" != "true" ]]; then
1253
+ read -r -p "Install OpenCode via $installer? [Y/n]: " install_oc || install_oc="Y"
1254
+ fi
1255
+ if [[ "$install_oc" =~ ^[Yy]?$ ]]; then
1256
+ if run_with_spinner "Installing OpenCode" npm_global_install "$install_pkg"; then
1257
+ print_success "OpenCode installed"
1258
+
1259
+ # Offer authentication
1260
+ echo ""
1261
+ print_info "OpenCode needs authentication to use AI models."
1262
+ print_info "Run 'opencode auth login' to authenticate."
1263
+ echo ""
1264
+ else
1265
+ print_warning "OpenCode installation failed"
1266
+ print_info "Try manually: sudo npm install -g $install_pkg"
1267
+ fi
1268
+ else
1269
+ print_info "Skipped OpenCode installation"
1270
+ print_info "Install later: $installer install -g $install_pkg"
1271
+ fi
1272
+
1273
+ return 0
1274
+ }
1275
+
1276
+ setup_orbstack_vm() {
1277
+ # Only available on macOS
1278
+ if [[ "$(uname)" != "Darwin" ]]; then
1279
+ return 0
1280
+ fi
1281
+
1282
+ # Check if OrbStack is already installed
1283
+ if [[ -d "/Applications/OrbStack.app" ]] || command -v orb >/dev/null 2>&1; then
1284
+ print_success "OrbStack already installed"
1285
+ return 0
1286
+ fi
1287
+
1288
+ print_info "OrbStack provides fast, lightweight Linux VMs on macOS"
1289
+ echo " You can run aidevops in an isolated Linux environment."
1290
+ echo " This is optional - aidevops works natively on macOS too."
1291
+ echo ""
1292
+
1293
+ if ! command -v brew >/dev/null 2>&1; then
1294
+ print_info "OrbStack available at: https://orbstack.dev/"
1295
+ return 0
1296
+ fi
1297
+
1298
+ read -r -p "Install OrbStack? [y/N]: " install_orb
1299
+ if [[ "$install_orb" =~ ^[Yy]$ ]]; then
1300
+ if run_with_spinner "Installing OrbStack" brew install --cask orbstack; then
1301
+ print_success "OrbStack installed"
1302
+ print_info "Create a VM: orb create ubuntu aidevops"
1303
+ print_info "Then install aidevops inside: orb run aidevops bash <(curl -fsSL https://aidevops.sh/install)"
1304
+ else
1305
+ print_warning "OrbStack installation failed"
1306
+ print_info "Download manually: https://orbstack.dev/"
1307
+ fi
1308
+ else
1309
+ print_info "Skipped OrbStack installation"
1310
+ fi
1311
+
1312
+ return 0
1313
+ }
1314
+
1315
+ setup_ai_orchestration() {
1316
+ print_info "Setting up AI orchestration frameworks..."
1317
+
1318
+ local has_python=false
1319
+
1320
+ # Check Python (prefer Homebrew/pyenv over system)
1321
+ local python3_bin
1322
+ if python3_bin=$(find_python3); then
1323
+ local python_version
1324
+ python_version=$("$python3_bin" --version 2>&1 | cut -d' ' -f2)
1325
+ local major minor
1326
+ major=$(echo "$python_version" | cut -d. -f1)
1327
+ minor=$(echo "$python_version" | cut -d. -f2)
1328
+
1329
+ if [[ $major -ge 3 ]] && [[ $minor -ge 10 ]]; then
1330
+ has_python=true
1331
+ print_success "Python $python_version found (3.10+ required)"
1332
+ else
1333
+ print_warning "Python 3.10+ required for AI orchestration, found $python_version"
1334
+ echo ""
1335
+ echo " Upgrade options:"
1336
+ echo " macOS (Homebrew): brew install python@3.12"
1337
+ echo " macOS (pyenv): pyenv install 3.12 && pyenv global 3.12"
1338
+ echo " Ubuntu/Debian: sudo apt install python3.12"
1339
+ echo " Fedora: sudo dnf install python3.12"
1340
+ echo ""
1341
+ fi
1342
+ else
1343
+ print_warning "Python 3 not found - AI orchestration frameworks unavailable"
1344
+ echo ""
1345
+ echo " Install options:"
1346
+ echo " macOS: brew install python@3.12"
1347
+ echo " Linux: sudo apt install python3 (or dnf/pacman)"
1348
+ echo ""
1349
+ return 0
1350
+ fi
1351
+
1352
+ if [[ "$has_python" == "false" ]]; then
1353
+ return 0
1354
+ fi
1355
+
1356
+ # Create orchestration directory
1357
+ mkdir -p "$HOME/.aidevops/orchestration"
1358
+
1359
+ # Info about available frameworks
1360
+ print_info "AI Orchestration Frameworks available:"
1361
+ echo " - Langflow: Visual flow builder (localhost:7860)"
1362
+ echo " - CrewAI: Multi-agent teams (localhost:8501)"
1363
+ echo " - AutoGen: Microsoft agentic AI (localhost:8081)"
1364
+ echo ""
1365
+ print_info "Setup individual frameworks with:"
1366
+ echo " bash .agents/scripts/langflow-helper.sh setup"
1367
+ echo " bash .agents/scripts/crewai-helper.sh setup"
1368
+ echo " bash .agents/scripts/autogen-helper.sh setup"
1369
+ echo ""
1370
+ print_info "See .agents/tools/ai-orchestration/overview.md for comparison"
1371
+
1372
+ return 0
1373
+ }