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.
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +2 -1
- package/scripts/npm-postinstall.cjs +13 -2
- package/setup-modules/agent-deploy.sh +627 -0
- package/setup-modules/config.sh +183 -0
- package/setup-modules/core.sh +572 -0
- package/setup-modules/mcp-setup.sh +766 -0
- package/setup-modules/migrations.sh +961 -0
- package/setup-modules/plugins.sh +588 -0
- package/setup-modules/shell-env.sh +892 -0
- package/setup-modules/tool-install.sh +1373 -0
- package/setup.sh +1 -1
|
@@ -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
|
+
}
|