aidevops 3.13.95 → 3.14.1
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/README.md +0 -1
- package/VERSION +1 -1
- package/aidevops.sh +44 -26
- package/package.json +1 -1
- package/setup.sh +25 -21
- package/aidevops-init-lib.sh +0 -1411
- package/aidevops-repos-lib.sh +0 -700
- package/aidevops-skills-plugin-lib.sh +0 -697
- package/aidevops-status-lib.sh +0 -141
- package/aidevops-update-lib.sh +0 -512
- package/aidevops-upgrade-planning-lib.sh +0 -370
- package/setup-modules/agent-deploy.sh +0 -1035
- package/setup-modules/agent-runtime.sh +0 -287
- package/setup-modules/config.sh +0 -478
- package/setup-modules/core.sh +0 -736
- package/setup-modules/mcp-setup.sh +0 -947
- package/setup-modules/migrations.sh +0 -1688
- package/setup-modules/plugins.sh +0 -728
- package/setup-modules/post-setup.sh +0 -301
- package/setup-modules/schedulers-linux.sh +0 -386
- package/setup-modules/schedulers-platform.sh +0 -1072
- package/setup-modules/schedulers-pulse.sh +0 -978
- package/setup-modules/schedulers.sh +0 -565
- package/setup-modules/shell-env.sh +0 -1240
- package/setup-modules/tool-beads.sh +0 -324
- package/setup-modules/tool-install.sh +0 -2134
|
@@ -1,2134 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
|
|
4
|
-
# Tool installation functions: git-clis, fd, ripgrep, shellcheck, shfmt, rosetta, worktrunk, minisim, recommended-tools, nodejs, python, orbstack
|
|
5
|
-
# Part of aidevops setup.sh modularization (t316.3)
|
|
6
|
-
|
|
7
|
-
# Shell safety baseline
|
|
8
|
-
set -Eeuo pipefail
|
|
9
|
-
IFS=$'\n\t'
|
|
10
|
-
# shellcheck disable=SC2154 # rc is assigned by $? in the trap string
|
|
11
|
-
trap 'rc=$?; echo "[ERROR] ${BASH_SOURCE[0]}:${LINENO} exit $rc" >&2' ERR
|
|
12
|
-
shopt -s inherit_errexit 2>/dev/null || true
|
|
13
|
-
|
|
14
|
-
setup_git_clis() {
|
|
15
|
-
print_info "Setting up Git CLI tools..."
|
|
16
|
-
|
|
17
|
-
local cli_tools=()
|
|
18
|
-
local missing_packages=()
|
|
19
|
-
local missing_names=()
|
|
20
|
-
|
|
21
|
-
# Check for GitHub CLI
|
|
22
|
-
if ! command -v gh >/dev/null 2>&1; then
|
|
23
|
-
missing_packages+=("gh")
|
|
24
|
-
missing_names+=("GitHub CLI")
|
|
25
|
-
else
|
|
26
|
-
cli_tools+=("GitHub CLI")
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# Check for GitLab CLI
|
|
30
|
-
if ! command -v glab >/dev/null 2>&1; then
|
|
31
|
-
missing_packages+=("glab")
|
|
32
|
-
missing_names+=("GitLab CLI")
|
|
33
|
-
else
|
|
34
|
-
cli_tools+=("GitLab CLI")
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
# Report found tools
|
|
38
|
-
if [[ ${#cli_tools[@]} -gt 0 ]]; then
|
|
39
|
-
print_success "Found Git CLI tools: ${cli_tools[*]}"
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Offer to install missing tools
|
|
43
|
-
if [[ ${#missing_packages[@]} -gt 0 ]]; then
|
|
44
|
-
print_warning "Missing Git CLI tools: ${missing_names[*]}"
|
|
45
|
-
echo " These provide enhanced Git platform integration (repos, PRs, issues)"
|
|
46
|
-
|
|
47
|
-
local pkg_manager
|
|
48
|
-
pkg_manager=$(detect_package_manager)
|
|
49
|
-
|
|
50
|
-
if [[ "$pkg_manager" != "unknown" ]]; then
|
|
51
|
-
echo ""
|
|
52
|
-
setup_prompt install_git_clis "Install Git CLI tools (${missing_packages[*]}) using $pkg_manager? [Y/n]: " "Y"
|
|
53
|
-
|
|
54
|
-
# shellcheck disable=SC2154 # set indirectly by setup_prompt via read
|
|
55
|
-
if [[ "$install_git_clis" =~ ^[Yy]?$ ]]; then
|
|
56
|
-
print_info "Installing ${missing_packages[*]}..."
|
|
57
|
-
if install_packages "$pkg_manager" "${missing_packages[@]}"; then
|
|
58
|
-
print_success "Git CLI tools installed"
|
|
59
|
-
echo ""
|
|
60
|
-
echo "📋 Next steps - authenticate each CLI:"
|
|
61
|
-
for pkg in "${missing_packages[@]}"; do
|
|
62
|
-
case "$pkg" in
|
|
63
|
-
gh) echo " • gh auth login -s workflow (workflow scope required for CI PRs)" ;;
|
|
64
|
-
glab) echo " • glab auth login" ;;
|
|
65
|
-
esac
|
|
66
|
-
done
|
|
67
|
-
else
|
|
68
|
-
print_warning "Failed to install some Git CLI tools (non-critical)"
|
|
69
|
-
fi
|
|
70
|
-
else
|
|
71
|
-
print_info "Skipped Git CLI tools installation"
|
|
72
|
-
echo ""
|
|
73
|
-
echo "📋 Manual installation:"
|
|
74
|
-
echo " macOS: brew install ${missing_packages[*]}"
|
|
75
|
-
echo " Ubuntu: sudo apt install ${missing_packages[*]}"
|
|
76
|
-
echo " Fedora: sudo dnf install ${missing_packages[*]}"
|
|
77
|
-
fi
|
|
78
|
-
else
|
|
79
|
-
echo ""
|
|
80
|
-
echo "📋 Manual installation:"
|
|
81
|
-
echo " macOS: brew install ${missing_packages[*]}"
|
|
82
|
-
echo " Ubuntu: sudo apt install ${missing_packages[*]}"
|
|
83
|
-
echo " Fedora: sudo dnf install ${missing_packages[*]}"
|
|
84
|
-
fi
|
|
85
|
-
else
|
|
86
|
-
print_success "All Git CLI tools installed and ready!"
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# Check for Gitea CLI separately (not in standard package managers)
|
|
90
|
-
if ! command -v tea >/dev/null 2>&1; then
|
|
91
|
-
print_info "Gitea CLI (tea) not found - install manually if needed:"
|
|
92
|
-
echo " go install code.gitea.io/tea/cmd/tea@latest"
|
|
93
|
-
echo " Or download from: https://dl.gitea.io/tea/"
|
|
94
|
-
else
|
|
95
|
-
print_success "Gitea CLI (tea) found"
|
|
96
|
-
fi
|
|
97
|
-
|
|
98
|
-
return 0
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
_print_file_discovery_manual_install() {
|
|
102
|
-
echo ""
|
|
103
|
-
echo " Manual installation:"
|
|
104
|
-
echo " macOS: brew install fd ripgrep ripgrep-all"
|
|
105
|
-
echo " Ubuntu/Debian: sudo apt install fd-find ripgrep # rga: cargo install ripgrep_all"
|
|
106
|
-
echo " Fedora: sudo dnf install fd-find ripgrep # rga: cargo install ripgrep_all"
|
|
107
|
-
echo " Arch: sudo pacman -S fd ripgrep ripgrep-all"
|
|
108
|
-
return 0
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
# Add fd=fdfind alias to shell rc files on Debian/Ubuntu after apt install.
|
|
112
|
-
_add_fd_alias_debian() {
|
|
113
|
-
local rc_files=("$HOME/.bashrc" "$HOME/.zshrc")
|
|
114
|
-
local added_to=""
|
|
115
|
-
local rc_file
|
|
116
|
-
|
|
117
|
-
for rc_file in "${rc_files[@]}"; do
|
|
118
|
-
[[ ! -f "$rc_file" ]] && continue
|
|
119
|
-
|
|
120
|
-
if ! grep -q 'alias fd="fdfind"' "$rc_file" 2>/dev/null; then
|
|
121
|
-
if { echo '' >>"$rc_file" &&
|
|
122
|
-
echo '# fd-find alias for Debian/Ubuntu (added by aidevops)' >>"$rc_file" &&
|
|
123
|
-
echo 'alias fd="fdfind"' >>"$rc_file"; }; then
|
|
124
|
-
added_to="${added_to:+$added_to, }$rc_file"
|
|
125
|
-
fi
|
|
126
|
-
fi
|
|
127
|
-
done
|
|
128
|
-
|
|
129
|
-
if [[ -n "$added_to" ]]; then
|
|
130
|
-
print_success "Added alias fd=fdfind to: $added_to"
|
|
131
|
-
echo " Restart your shell to activate"
|
|
132
|
-
else
|
|
133
|
-
print_success "fd alias already configured"
|
|
134
|
-
fi
|
|
135
|
-
return 0
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# Resolve apt package names (fd→fd-find on Debian/Ubuntu) and install.
|
|
139
|
-
_install_file_discovery_packages() {
|
|
140
|
-
local pkg_manager="$1"
|
|
141
|
-
shift
|
|
142
|
-
local missing_packages=("$@")
|
|
143
|
-
|
|
144
|
-
print_info "Installing ${missing_packages[*]}..."
|
|
145
|
-
|
|
146
|
-
local actual_packages=()
|
|
147
|
-
local pkg
|
|
148
|
-
for pkg in "${missing_packages[@]}"; do
|
|
149
|
-
case "$pkg_manager" in
|
|
150
|
-
apt)
|
|
151
|
-
# Debian/Ubuntu uses fd-find instead of fd
|
|
152
|
-
if [[ "$pkg" == "fd" ]]; then
|
|
153
|
-
actual_packages+=("fd-find")
|
|
154
|
-
else
|
|
155
|
-
actual_packages+=("$pkg")
|
|
156
|
-
fi
|
|
157
|
-
;;
|
|
158
|
-
*)
|
|
159
|
-
actual_packages+=("$pkg")
|
|
160
|
-
;;
|
|
161
|
-
esac
|
|
162
|
-
done
|
|
163
|
-
|
|
164
|
-
if install_packages "$pkg_manager" "${actual_packages[@]}"; then
|
|
165
|
-
print_success "File discovery tools installed"
|
|
166
|
-
# On Debian/Ubuntu, fd is installed as fdfind — create alias in shell rc files
|
|
167
|
-
if [[ "$pkg_manager" == "apt" ]] && command -v fdfind >/dev/null 2>&1 && ! command -v fd >/dev/null 2>&1; then
|
|
168
|
-
_add_fd_alias_debian
|
|
169
|
-
fi
|
|
170
|
-
else
|
|
171
|
-
print_warning "Failed to install some file discovery tools (non-critical)"
|
|
172
|
-
fi
|
|
173
|
-
return 0
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
setup_file_discovery_tools() {
|
|
177
|
-
print_info "Setting up file discovery tools..."
|
|
178
|
-
|
|
179
|
-
local missing_tools=()
|
|
180
|
-
local missing_packages=()
|
|
181
|
-
local missing_names=()
|
|
182
|
-
|
|
183
|
-
local fd_version
|
|
184
|
-
if command -v fd >/dev/null 2>&1; then
|
|
185
|
-
fd_version=$(fd --version 2>/dev/null | head -1 || echo "unknown")
|
|
186
|
-
print_success "fd found: $fd_version"
|
|
187
|
-
elif command -v fdfind >/dev/null 2>&1; then
|
|
188
|
-
fd_version=$(fdfind --version 2>/dev/null | head -1 || echo "unknown")
|
|
189
|
-
print_success "fd found (as fdfind): $fd_version"
|
|
190
|
-
print_warning "Note: 'fd' alias not active in current shell. Restart shell or run: alias fd=fdfind"
|
|
191
|
-
else
|
|
192
|
-
missing_tools+=("fd")
|
|
193
|
-
missing_packages+=("fd")
|
|
194
|
-
missing_names+=("fd (fast file finder)")
|
|
195
|
-
fi
|
|
196
|
-
|
|
197
|
-
# Check for ripgrep
|
|
198
|
-
if ! command -v rg >/dev/null 2>&1; then
|
|
199
|
-
missing_tools+=("rg")
|
|
200
|
-
missing_packages+=("ripgrep")
|
|
201
|
-
missing_names+=("ripgrep (fast content search)")
|
|
202
|
-
else
|
|
203
|
-
local rg_version
|
|
204
|
-
rg_version=$(rg --version 2>/dev/null | head -1 || echo "unknown")
|
|
205
|
-
print_success "ripgrep found: $rg_version"
|
|
206
|
-
fi
|
|
207
|
-
|
|
208
|
-
# Check for ripgrep-all (searches inside PDFs, DOCX, SQLite, archives)
|
|
209
|
-
if ! command -v rga >/dev/null 2>&1; then
|
|
210
|
-
missing_tools+=("rga")
|
|
211
|
-
missing_packages+=("ripgrep-all")
|
|
212
|
-
missing_names+=("ripgrep-all (search inside PDFs/docs/archives)")
|
|
213
|
-
else
|
|
214
|
-
local rga_version
|
|
215
|
-
rga_version=$(rga --version 2>/dev/null | head -1 || echo "unknown")
|
|
216
|
-
print_success "ripgrep-all found: $rga_version"
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
# Offer to install missing tools
|
|
220
|
-
if [[ ${#missing_tools[@]} -gt 0 ]]; then
|
|
221
|
-
print_warning "Missing file discovery tools: ${missing_names[*]}"
|
|
222
|
-
echo ""
|
|
223
|
-
echo " These tools provide 10x faster file discovery than built-in glob:"
|
|
224
|
-
echo " fd - Fast alternative to 'find', respects .gitignore"
|
|
225
|
-
echo " ripgrep - Fast alternative to 'grep', respects .gitignore"
|
|
226
|
-
echo " ripgrep-all - Extends ripgrep to search inside PDFs, DOCX, SQLite, archives"
|
|
227
|
-
echo ""
|
|
228
|
-
echo " AI agents use these for efficient codebase navigation."
|
|
229
|
-
echo ""
|
|
230
|
-
|
|
231
|
-
local pkg_manager
|
|
232
|
-
pkg_manager=$(detect_package_manager)
|
|
233
|
-
|
|
234
|
-
if [[ "$pkg_manager" != "unknown" ]]; then
|
|
235
|
-
setup_prompt install_fd_tools "Install file discovery tools (${missing_packages[*]}) using $pkg_manager? [Y/n]: " "Y"
|
|
236
|
-
|
|
237
|
-
# shellcheck disable=SC2154 # set indirectly by setup_prompt via read
|
|
238
|
-
if [[ "$install_fd_tools" =~ ^[Yy]?$ ]]; then
|
|
239
|
-
_install_file_discovery_packages "$pkg_manager" "${missing_packages[@]}"
|
|
240
|
-
else
|
|
241
|
-
print_info "Skipped file discovery tools installation"
|
|
242
|
-
_print_file_discovery_manual_install
|
|
243
|
-
fi
|
|
244
|
-
else
|
|
245
|
-
_print_file_discovery_manual_install
|
|
246
|
-
fi
|
|
247
|
-
else
|
|
248
|
-
print_success "All file discovery tools installed!"
|
|
249
|
-
fi
|
|
250
|
-
|
|
251
|
-
return 0
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
setup_rtk() {
|
|
255
|
-
# rtk — CLI proxy that reduces LLM token consumption by 60-90% (t1430)
|
|
256
|
-
# Optional optimization: compresses git/gh/test outputs before they reach LLM context
|
|
257
|
-
# Single Rust binary, zero dependencies, <10ms overhead
|
|
258
|
-
# https://github.com/rtk-ai/rtk
|
|
259
|
-
|
|
260
|
-
# Pin to a tagged release for stability and auditability (Gemini review feedback).
|
|
261
|
-
# Update the tag when upstream-watch detects a new release.
|
|
262
|
-
local rtk_installer_url="https://raw.githubusercontent.com/rtk-ai/rtk/v0.28.2/install.sh"
|
|
263
|
-
|
|
264
|
-
if command -v rtk >/dev/null 2>&1; then
|
|
265
|
-
local rtk_version
|
|
266
|
-
rtk_version=$(rtk --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
|
|
267
|
-
print_success "rtk found: v$rtk_version (token optimization proxy)"
|
|
268
|
-
# Fall through to ensure config is applied (telemetry, tee)
|
|
269
|
-
else
|
|
270
|
-
print_info "rtk (Rust Token Killer) reduces LLM token usage by 60-90% on CLI commands"
|
|
271
|
-
echo " Compresses git, gh, test runner, and linter outputs before they reach the AI context."
|
|
272
|
-
echo " Single binary, zero dependencies, <10ms overhead."
|
|
273
|
-
echo ""
|
|
274
|
-
|
|
275
|
-
setup_prompt install_rtk "Install rtk for token-optimized CLI output? [y/N]: " "n"
|
|
276
|
-
|
|
277
|
-
# shellcheck disable=SC2154 # set indirectly by setup_prompt via read
|
|
278
|
-
if [[ "$install_rtk" =~ ^[Yy]$ ]]; then
|
|
279
|
-
VERIFIED_INSTALL_SHELL="sh"
|
|
280
|
-
if command -v brew >/dev/null 2>&1; then
|
|
281
|
-
if run_with_spinner "Installing rtk via Homebrew" brew install rtk; then
|
|
282
|
-
print_success "rtk installed via Homebrew"
|
|
283
|
-
else
|
|
284
|
-
print_warning "Homebrew install failed, trying curl installer..."
|
|
285
|
-
if verified_install "rtk" "$rtk_installer_url"; then
|
|
286
|
-
print_success "rtk installed to ~/.local/bin/rtk"
|
|
287
|
-
else
|
|
288
|
-
print_warning "rtk installation failed (non-critical, optional tool)"
|
|
289
|
-
fi
|
|
290
|
-
fi
|
|
291
|
-
else
|
|
292
|
-
# Linux or macOS without brew — use verified_install for secure execution
|
|
293
|
-
if verified_install "rtk" "$rtk_installer_url"; then
|
|
294
|
-
print_success "rtk installed to ~/.local/bin/rtk"
|
|
295
|
-
else
|
|
296
|
-
print_warning "rtk installation failed (non-critical, optional tool)"
|
|
297
|
-
echo " Manual install: https://github.com/rtk-ai/rtk#installation"
|
|
298
|
-
fi
|
|
299
|
-
fi
|
|
300
|
-
else
|
|
301
|
-
print_info "Skipped rtk installation (optional)"
|
|
302
|
-
echo " Manual install: brew install rtk OR curl -fsSL $rtk_installer_url | sh"
|
|
303
|
-
fi
|
|
304
|
-
fi
|
|
305
|
-
|
|
306
|
-
# Configure rtk (telemetry off, tee for failure capture) — only if binary is present
|
|
307
|
-
if command -v rtk >/dev/null 2>&1; then
|
|
308
|
-
local rtk_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/rtk"
|
|
309
|
-
if [[ ! -f "$rtk_config_dir/config.toml" ]]; then
|
|
310
|
-
mkdir -p "$rtk_config_dir"
|
|
311
|
-
cat >"$rtk_config_dir/config.toml" <<-'RTKEOF'
|
|
312
|
-
# rtk configuration (created by aidevops setup.sh)
|
|
313
|
-
# https://github.com/rtk-ai/rtk
|
|
314
|
-
|
|
315
|
-
[telemetry]
|
|
316
|
-
enabled = false
|
|
317
|
-
|
|
318
|
-
[tee]
|
|
319
|
-
enabled = true
|
|
320
|
-
mode = "failures"
|
|
321
|
-
max_files = 20
|
|
322
|
-
RTKEOF
|
|
323
|
-
print_success "rtk config created (telemetry disabled)"
|
|
324
|
-
fi
|
|
325
|
-
fi
|
|
326
|
-
|
|
327
|
-
return 0
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
setup_shell_linting_tools() {
|
|
331
|
-
print_info "Setting up shell linting tools..."
|
|
332
|
-
|
|
333
|
-
local missing_tools=()
|
|
334
|
-
local pkg_manager
|
|
335
|
-
pkg_manager=$(detect_package_manager)
|
|
336
|
-
|
|
337
|
-
# Check shellcheck
|
|
338
|
-
if command -v shellcheck >/dev/null 2>&1; then
|
|
339
|
-
local sc_version sc_rosetta=false
|
|
340
|
-
sc_version=$(shellcheck --version 2>/dev/null | grep 'version:' | awk '{print $2}' || echo "unknown")
|
|
341
|
-
# Rosetta detection (macOS Apple Silicon only, requires `file` command)
|
|
342
|
-
if [[ "$PLATFORM_MACOS" == "true" ]] && [[ "$PLATFORM_ARM64" == "true" ]] && command -v file >/dev/null 2>&1; then
|
|
343
|
-
local sc_file_output
|
|
344
|
-
sc_file_output=$(file "$(command -v shellcheck)" 2>/dev/null || echo "")
|
|
345
|
-
if [[ "$sc_file_output" == *"x86_64"* ]] && [[ "$sc_file_output" != *"arm64"* ]]; then
|
|
346
|
-
sc_rosetta=true
|
|
347
|
-
fi
|
|
348
|
-
fi
|
|
349
|
-
if [[ "$sc_rosetta" == "true" ]]; then
|
|
350
|
-
print_warning "shellcheck found but running under Rosetta (x86_64)"
|
|
351
|
-
print_info " Run 'rosetta-audit-helper.sh migrate' to fix"
|
|
352
|
-
else
|
|
353
|
-
print_success "shellcheck found ($sc_version)"
|
|
354
|
-
fi
|
|
355
|
-
else
|
|
356
|
-
missing_tools+=("shellcheck")
|
|
357
|
-
fi
|
|
358
|
-
|
|
359
|
-
# Check shfmt
|
|
360
|
-
if command -v shfmt >/dev/null 2>&1; then
|
|
361
|
-
print_success "shfmt found ($(shfmt --version 2>/dev/null))"
|
|
362
|
-
else
|
|
363
|
-
missing_tools+=("shfmt")
|
|
364
|
-
fi
|
|
365
|
-
|
|
366
|
-
if [[ ${#missing_tools[@]} -gt 0 ]]; then
|
|
367
|
-
print_warning "Missing shell linting tools: ${missing_tools[*]}"
|
|
368
|
-
echo " shellcheck - static analysis for shell scripts"
|
|
369
|
-
echo " shfmt - shell script formatter (fast syntax checks)"
|
|
370
|
-
|
|
371
|
-
if [[ "$pkg_manager" != "unknown" ]]; then
|
|
372
|
-
local install_linters
|
|
373
|
-
setup_prompt install_linters "Install missing shell linting tools using $pkg_manager? [Y/n]: " "Y"
|
|
374
|
-
|
|
375
|
-
if [[ "$install_linters" =~ ^[Yy]?$ ]]; then
|
|
376
|
-
if install_packages "$pkg_manager" "${missing_tools[@]}"; then
|
|
377
|
-
print_success "Shell linting tools installed"
|
|
378
|
-
else
|
|
379
|
-
print_warning "Failed to install some shell linting tools"
|
|
380
|
-
fi
|
|
381
|
-
else
|
|
382
|
-
print_info "Skipped shell linting tools"
|
|
383
|
-
fi
|
|
384
|
-
else
|
|
385
|
-
echo " Install manually:"
|
|
386
|
-
echo " macOS: brew install ${missing_tools[*]}"
|
|
387
|
-
echo " Linux: apt install ${missing_tools[*]}"
|
|
388
|
-
fi
|
|
389
|
-
fi
|
|
390
|
-
|
|
391
|
-
return 0
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
setup_setsid_advisory() {
|
|
395
|
-
# setsid is required to detach pulse workers into their own process group
|
|
396
|
-
# (t2757, GH#20561, GH#21102). Without it, workers inherit pulse's PGID and
|
|
397
|
-
# are killed by any PG-scoped signal (launchd unload, restart chain).
|
|
398
|
-
#
|
|
399
|
-
# Linux: setsid ships with util-linux (present on all mainstream distros).
|
|
400
|
-
# macOS: available from macOS 12+ at /usr/bin/setsid. Older macOS or systems
|
|
401
|
-
# where /usr/bin/setsid is absent need util-linux via Homebrew.
|
|
402
|
-
# util-linux is keg-only on Homebrew — binary is not linked into PATH
|
|
403
|
-
# automatically, so we create a symlink after install.
|
|
404
|
-
if command -v setsid >/dev/null 2>&1; then
|
|
405
|
-
local setsid_path
|
|
406
|
-
setsid_path="$(command -v setsid)"
|
|
407
|
-
print_success "setsid found at $setsid_path (worker process-group isolation enabled)"
|
|
408
|
-
return 0
|
|
409
|
-
fi
|
|
410
|
-
|
|
411
|
-
# setsid missing — on macOS with Homebrew, auto-install util-linux and
|
|
412
|
-
# symlink setsid into PATH (GH#21102 / t2926). On Linux and macOS without
|
|
413
|
-
# Homebrew, emit an actionable error with install instructions.
|
|
414
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
415
|
-
if command -v brew >/dev/null 2>&1; then
|
|
416
|
-
print_info "setsid not found — installing util-linux for worker PGID isolation (GH#21102)"
|
|
417
|
-
if brew install util-linux 2>&1 | tail -3; then
|
|
418
|
-
# util-linux is keg-only: binary lives under the keg, not in /opt/homebrew/bin.
|
|
419
|
-
# Symlink setsid into a standard PATH directory so 'command -v setsid' works.
|
|
420
|
-
local brew_prefix
|
|
421
|
-
brew_prefix="$(brew --prefix 2>/dev/null || echo "")"
|
|
422
|
-
local keg_setsid="${brew_prefix}/opt/util-linux/bin/setsid"
|
|
423
|
-
local link_target="${brew_prefix}/bin/setsid"
|
|
424
|
-
if [[ -x "$keg_setsid" && ! -e "$link_target" ]]; then
|
|
425
|
-
ln -s "$keg_setsid" "$link_target" && \
|
|
426
|
-
print_success "Symlinked setsid: $keg_setsid → $link_target"
|
|
427
|
-
fi
|
|
428
|
-
# Verify setsid is now reachable
|
|
429
|
-
if command -v setsid >/dev/null 2>&1; then
|
|
430
|
-
print_success "setsid installed at $(command -v setsid) (worker PGID isolation enabled)"
|
|
431
|
-
else
|
|
432
|
-
print_error "util-linux installed but setsid still not in PATH — check brew --prefix"
|
|
433
|
-
fi
|
|
434
|
-
else
|
|
435
|
-
print_error "brew install util-linux failed — workers will share pulse PGID until resolved"
|
|
436
|
-
echo " Manual fix: brew install util-linux"
|
|
437
|
-
fi
|
|
438
|
-
else
|
|
439
|
-
print_error "setsid not found — worker isolation broken; install util-linux"
|
|
440
|
-
echo " Impact: every pulse restart sends SIGHUP to workers in its PGID,"
|
|
441
|
-
echo " killing in-flight workers before they can finish (GH#21102)"
|
|
442
|
-
echo " Fix: install Homebrew, then run: brew install util-linux"
|
|
443
|
-
echo " Or upgrade to macOS 12+ where /usr/bin/setsid ships by default"
|
|
444
|
-
fi
|
|
445
|
-
else
|
|
446
|
-
# Linux: setsid should be present on all mainstream distros via util-linux.
|
|
447
|
-
# If it is missing, emit an error rather than a warning — workers will be
|
|
448
|
-
# killed on every pulse cycle restart without it.
|
|
449
|
-
print_error "setsid not found — worker isolation broken; install util-linux"
|
|
450
|
-
echo " Impact: every pulse restart sends SIGHUP to workers in its PGID,"
|
|
451
|
-
echo " killing in-flight workers before they can finish (GH#21102)"
|
|
452
|
-
echo " Fix: sudo apt install util-linux # Debian/Ubuntu"
|
|
453
|
-
echo " sudo dnf install util-linux # Fedora/RHEL"
|
|
454
|
-
echo " sudo pacman -S util-linux # Arch"
|
|
455
|
-
echo " sudo apk add util-linux # Alpine"
|
|
456
|
-
fi
|
|
457
|
-
echo ""
|
|
458
|
-
|
|
459
|
-
return 0
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
setup_shellcheck_wrapper() {
|
|
463
|
-
# Replace the real shellcheck binary with our wrapper script to prevent
|
|
464
|
-
# --external-sources from causing exponential memory growth (GH#2915).
|
|
465
|
-
# This intercepts ALL callers including compiled binaries (e.g., OpenCode)
|
|
466
|
-
# that invoke shellcheck by absolute path rather than via PATH.
|
|
467
|
-
|
|
468
|
-
local wrapper_src="${INSTALL_DIR:-.}/.agents/scripts/shellcheck-wrapper.sh"
|
|
469
|
-
if [[ ! -f "$wrapper_src" ]]; then
|
|
470
|
-
print_info "shellcheck-wrapper.sh not found — skipping binary replacement"
|
|
471
|
-
return 0
|
|
472
|
-
fi
|
|
473
|
-
|
|
474
|
-
# Find the real shellcheck binary
|
|
475
|
-
local sc_path
|
|
476
|
-
sc_path="$(command -v shellcheck 2>/dev/null || true)"
|
|
477
|
-
if [[ -z "$sc_path" ]]; then
|
|
478
|
-
print_info "shellcheck not installed — wrapper not needed yet"
|
|
479
|
-
return 0
|
|
480
|
-
fi
|
|
481
|
-
|
|
482
|
-
# Resolve symlinks to get the actual binary path
|
|
483
|
-
local sc_resolved
|
|
484
|
-
sc_resolved="$(realpath "$sc_path" 2>/dev/null || readlink -f "$sc_path" 2>/dev/null || echo "$sc_path")"
|
|
485
|
-
|
|
486
|
-
# Check if the binary is already our wrapper (idempotent)
|
|
487
|
-
if head -5 "$sc_resolved" 2>/dev/null | grep -q "shellcheck-wrapper" 2>/dev/null; then
|
|
488
|
-
# Already replaced — check that .real exists
|
|
489
|
-
local real_path="${sc_resolved}.real"
|
|
490
|
-
if [[ ! -x "$real_path" ]]; then
|
|
491
|
-
print_warning "shellcheck wrapper installed but .real binary missing at $real_path"
|
|
492
|
-
print_info "Reinstall shellcheck (brew reinstall shellcheck) then re-run setup"
|
|
493
|
-
return 0
|
|
494
|
-
fi
|
|
495
|
-
|
|
496
|
-
# Check if the installed wrapper is outdated vs the source
|
|
497
|
-
if ! diff -q "$wrapper_src" "$sc_resolved" >/dev/null 2>&1; then
|
|
498
|
-
print_info "Updating shellcheck wrapper at $sc_resolved (source is newer)"
|
|
499
|
-
if cp "$wrapper_src" "$sc_resolved" 2>/dev/null || sudo cp "$wrapper_src" "$sc_resolved" 2>/dev/null; then
|
|
500
|
-
chmod +x "$sc_resolved" 2>/dev/null || sudo chmod +x "$sc_resolved" 2>/dev/null || true
|
|
501
|
-
print_success "shellcheck wrapper updated at $sc_resolved"
|
|
502
|
-
else
|
|
503
|
-
print_warning "Cannot update wrapper — insufficient permissions"
|
|
504
|
-
fi
|
|
505
|
-
else
|
|
506
|
-
print_success "shellcheck wrapper already installed at $sc_resolved"
|
|
507
|
-
fi
|
|
508
|
-
return 0
|
|
509
|
-
fi
|
|
510
|
-
|
|
511
|
-
# The binary at sc_resolved is the real shellcheck — replace it
|
|
512
|
-
local real_dest="${sc_resolved}.real"
|
|
513
|
-
|
|
514
|
-
print_info "Installing shellcheck wrapper at $sc_resolved"
|
|
515
|
-
print_info " Real binary will be moved to $real_dest"
|
|
516
|
-
|
|
517
|
-
# Move real binary to .real suffix
|
|
518
|
-
if ! mv "$sc_resolved" "$real_dest" 2>/dev/null; then
|
|
519
|
-
# May need sudo (e.g., /usr/local/bin on some systems)
|
|
520
|
-
if ! sudo mv "$sc_resolved" "$real_dest" 2>/dev/null; then
|
|
521
|
-
print_warning "Cannot move shellcheck binary — insufficient permissions"
|
|
522
|
-
print_info "Run manually: sudo mv '$sc_resolved' '$real_dest'"
|
|
523
|
-
return 0
|
|
524
|
-
fi
|
|
525
|
-
fi
|
|
526
|
-
|
|
527
|
-
# Copy wrapper to the original path
|
|
528
|
-
if ! cp "$wrapper_src" "$sc_resolved" 2>/dev/null; then
|
|
529
|
-
if ! sudo cp "$wrapper_src" "$sc_resolved" 2>/dev/null; then
|
|
530
|
-
# Rollback
|
|
531
|
-
mv "$real_dest" "$sc_resolved" 2>/dev/null || sudo mv "$real_dest" "$sc_resolved" 2>/dev/null || true
|
|
532
|
-
print_warning "Cannot install wrapper — insufficient permissions"
|
|
533
|
-
return 0
|
|
534
|
-
fi
|
|
535
|
-
fi
|
|
536
|
-
|
|
537
|
-
# Ensure wrapper is executable
|
|
538
|
-
chmod +x "$sc_resolved" 2>/dev/null || sudo chmod +x "$sc_resolved" 2>/dev/null || true
|
|
539
|
-
|
|
540
|
-
print_success "shellcheck wrapper installed — --external-sources will be stripped"
|
|
541
|
-
print_info " Real binary: $real_dest"
|
|
542
|
-
print_info " Wrapper: $sc_resolved"
|
|
543
|
-
|
|
544
|
-
return 0
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
setup_qlty_cli() {
|
|
548
|
-
print_info "Setting up Qlty CLI (multi-linter code quality)..."
|
|
549
|
-
|
|
550
|
-
local qlty_bin="${HOME}/.qlty/bin/qlty"
|
|
551
|
-
|
|
552
|
-
# Check if already installed
|
|
553
|
-
if [[ -x "$qlty_bin" ]]; then
|
|
554
|
-
local qlty_version
|
|
555
|
-
qlty_version=$("$qlty_bin" --version 2>/dev/null | head -1 || echo "unknown")
|
|
556
|
-
print_success "Qlty CLI already installed: $qlty_version"
|
|
557
|
-
return 0
|
|
558
|
-
fi
|
|
559
|
-
|
|
560
|
-
# Also check PATH in case it's installed elsewhere
|
|
561
|
-
if command -v qlty >/dev/null 2>&1; then
|
|
562
|
-
local qlty_version
|
|
563
|
-
qlty_version=$(qlty --version 2>/dev/null | head -1 || echo "unknown")
|
|
564
|
-
print_success "Qlty CLI found in PATH: $qlty_version"
|
|
565
|
-
return 0
|
|
566
|
-
fi
|
|
567
|
-
|
|
568
|
-
print_info "Qlty provides universal code quality analysis for 40+ languages"
|
|
569
|
-
echo " - Runs 70+ static analysis tools (ShellCheck, ESLint, etc.)"
|
|
570
|
-
echo " - Detects code smells and maintainability issues"
|
|
571
|
-
echo " - Used by the daily code quality sweep (pulse-wrapper.sh)"
|
|
572
|
-
echo ""
|
|
573
|
-
|
|
574
|
-
local install_qlty
|
|
575
|
-
setup_prompt install_qlty "Install Qlty CLI? [Y/n]: " "Y"
|
|
576
|
-
|
|
577
|
-
if [[ "$install_qlty" =~ ^[Yy]?$ ]]; then
|
|
578
|
-
if command -v curl >/dev/null 2>&1; then
|
|
579
|
-
if verified_install "Qlty CLI" "https://qlty.sh"; then
|
|
580
|
-
# Verify installation
|
|
581
|
-
if [[ -x "$qlty_bin" ]]; then
|
|
582
|
-
local qlty_version
|
|
583
|
-
qlty_version=$("$qlty_bin" --version 2>/dev/null | head -1 || echo "unknown")
|
|
584
|
-
print_success "Qlty CLI installed: $qlty_version"
|
|
585
|
-
print_info "Ensure ~/.qlty/bin is in your PATH"
|
|
586
|
-
print_info "Documentation: ~/.aidevops/agents/tools/code-review/qlty.md"
|
|
587
|
-
elif command -v qlty >/dev/null 2>&1; then
|
|
588
|
-
print_success "Qlty CLI installed: $(qlty --version 2>/dev/null | head -1)"
|
|
589
|
-
else
|
|
590
|
-
print_warning "Qlty CLI install script ran but binary not found at $qlty_bin"
|
|
591
|
-
print_info "Try restarting your shell or check ~/.qlty/bin/"
|
|
592
|
-
fi
|
|
593
|
-
else
|
|
594
|
-
print_warning "Qlty CLI installation failed"
|
|
595
|
-
print_info "Install manually: curl -fsSL https://qlty.sh | bash"
|
|
596
|
-
fi
|
|
597
|
-
else
|
|
598
|
-
print_warning "curl not found — cannot install Qlty CLI"
|
|
599
|
-
print_info "Install manually: curl -fsSL https://qlty.sh | bash"
|
|
600
|
-
fi
|
|
601
|
-
else
|
|
602
|
-
print_info "Skipped Qlty CLI installation"
|
|
603
|
-
print_info "Install later: curl -fsSL https://qlty.sh | bash"
|
|
604
|
-
fi
|
|
605
|
-
|
|
606
|
-
return 0
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
setup_rosetta_audit() {
|
|
610
|
-
# Skip on non-Apple-Silicon or non-macOS
|
|
611
|
-
if [[ "$(uname)" != "Darwin" ]] || [[ "$(uname -m)" != "arm64" ]]; then
|
|
612
|
-
print_info "Rosetta audit: not applicable (Intel Mac or non-macOS)"
|
|
613
|
-
return 0
|
|
614
|
-
fi
|
|
615
|
-
|
|
616
|
-
# Skip if no dual-brew setup
|
|
617
|
-
if [[ ! -x "/usr/local/bin/brew" ]] || [[ ! -x "/opt/homebrew/bin/brew" ]]; then
|
|
618
|
-
print_success "Rosetta audit: clean Homebrew setup (no x86 brew detected)"
|
|
619
|
-
return 0
|
|
620
|
-
fi
|
|
621
|
-
|
|
622
|
-
print_info "Detected dual Homebrew (x86 + ARM) — checking for Rosetta overhead..."
|
|
623
|
-
|
|
624
|
-
local x86_only_count dup_count
|
|
625
|
-
dup_count=$(comm -12 \
|
|
626
|
-
<(/usr/local/bin/brew list --formula 2>/dev/null | sort) \
|
|
627
|
-
<(/opt/homebrew/bin/brew list --formula 2>/dev/null | sort) | wc -l | tr -d ' ')
|
|
628
|
-
x86_only_count=$(comm -23 \
|
|
629
|
-
<(/usr/local/bin/brew list --formula 2>/dev/null | sort) \
|
|
630
|
-
<(/opt/homebrew/bin/brew list --formula 2>/dev/null | sort) | wc -l | tr -d ' ')
|
|
631
|
-
|
|
632
|
-
local total=$((x86_only_count + dup_count))
|
|
633
|
-
|
|
634
|
-
if [[ "$total" -eq 0 ]]; then
|
|
635
|
-
print_success "No x86 Homebrew packages found — clean ARM setup"
|
|
636
|
-
return 0
|
|
637
|
-
fi
|
|
638
|
-
|
|
639
|
-
print_warning "Found $total x86 Homebrew packages ($x86_only_count x86-only, $dup_count duplicates)"
|
|
640
|
-
echo " These run under Rosetta 2 emulation with ~30% performance overhead"
|
|
641
|
-
echo ""
|
|
642
|
-
echo " To audit: rosetta-audit-helper.sh scan"
|
|
643
|
-
echo " To migrate: rosetta-audit-helper.sh migrate --dry-run"
|
|
644
|
-
echo " To fix: rosetta-audit-helper.sh migrate"
|
|
645
|
-
|
|
646
|
-
return 0
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
# Install Worktrunk shell integration (enables 'wt switch' to change directories).
|
|
650
|
-
_setup_worktrunk_shell_integration() {
|
|
651
|
-
print_info "Installing shell integration..."
|
|
652
|
-
if wt config shell install; then
|
|
653
|
-
print_success "Shell integration installed"
|
|
654
|
-
print_info "Restart your terminal or source your shell config"
|
|
655
|
-
else
|
|
656
|
-
print_warning "Shell integration failed - run manually: wt config shell install"
|
|
657
|
-
fi
|
|
658
|
-
return 0
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
# Check and optionally install Worktrunk shell integration when wt is already present.
|
|
662
|
-
_check_worktrunk_shell_integration() {
|
|
663
|
-
local wt_integrated=false
|
|
664
|
-
local rc_file
|
|
665
|
-
while IFS= read -r rc_file; do
|
|
666
|
-
[[ -z "$rc_file" ]] && continue
|
|
667
|
-
if [[ -f "$rc_file" ]] && grep -q "worktrunk" "$rc_file" 2>/dev/null; then
|
|
668
|
-
wt_integrated=true
|
|
669
|
-
break
|
|
670
|
-
fi
|
|
671
|
-
done < <(get_all_shell_rcs)
|
|
672
|
-
|
|
673
|
-
if [[ "$wt_integrated" == "false" ]]; then
|
|
674
|
-
print_info "Shell integration not detected"
|
|
675
|
-
local install_shell
|
|
676
|
-
setup_prompt install_shell "Install Worktrunk shell integration (enables 'wt switch' to change directories)? [Y/n]: " "Y"
|
|
677
|
-
if [[ "$install_shell" =~ ^[Yy]?$ ]]; then
|
|
678
|
-
_setup_worktrunk_shell_integration
|
|
679
|
-
fi
|
|
680
|
-
else
|
|
681
|
-
print_success "Shell integration already configured"
|
|
682
|
-
fi
|
|
683
|
-
return 0
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
# Install Worktrunk via Homebrew and set up shell integration.
|
|
687
|
-
_install_worktrunk_brew() {
|
|
688
|
-
local install_wt
|
|
689
|
-
setup_prompt install_wt "Install Worktrunk via Homebrew? [Y/n]: " "Y"
|
|
690
|
-
|
|
691
|
-
if [[ "$install_wt" =~ ^[Yy]?$ ]]; then
|
|
692
|
-
if run_with_spinner "Installing Worktrunk via Homebrew" brew install max-sixty/worktrunk/wt; then
|
|
693
|
-
_setup_worktrunk_shell_integration
|
|
694
|
-
echo ""
|
|
695
|
-
print_info "Quick start:"
|
|
696
|
-
echo " wt switch feature/my-feature # Create/switch to worktree"
|
|
697
|
-
echo " wt list # List all worktrees"
|
|
698
|
-
echo " wt merge # Merge and cleanup"
|
|
699
|
-
echo ""
|
|
700
|
-
print_info "Documentation: ~/.aidevops/agents/tools/git/worktrunk.md"
|
|
701
|
-
else
|
|
702
|
-
print_warning "Homebrew installation failed"
|
|
703
|
-
echo " Try: cargo install worktrunk && wt config shell install"
|
|
704
|
-
fi
|
|
705
|
-
else
|
|
706
|
-
print_info "Skipped Worktrunk installation"
|
|
707
|
-
print_info "Install later: brew install max-sixty/worktrunk/wt"
|
|
708
|
-
print_info "Fallback available: ~/.aidevops/agents/scripts/worktree-helper.sh"
|
|
709
|
-
fi
|
|
710
|
-
return 0
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
# Install Worktrunk via Cargo and set up shell integration.
|
|
714
|
-
_install_worktrunk_cargo() {
|
|
715
|
-
local install_wt
|
|
716
|
-
setup_prompt install_wt "Install Worktrunk via Cargo? [Y/n]: " "Y"
|
|
717
|
-
|
|
718
|
-
if [[ "$install_wt" =~ ^[Yy]?$ ]]; then
|
|
719
|
-
if run_with_spinner "Installing Worktrunk via Cargo" cargo install worktrunk; then
|
|
720
|
-
_setup_worktrunk_shell_integration
|
|
721
|
-
else
|
|
722
|
-
print_warning "Cargo installation failed"
|
|
723
|
-
fi
|
|
724
|
-
else
|
|
725
|
-
print_info "Skipped Worktrunk installation"
|
|
726
|
-
fi
|
|
727
|
-
return 0
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
setup_worktrunk() {
|
|
731
|
-
print_info "Setting up Worktrunk (git worktree management)..."
|
|
732
|
-
|
|
733
|
-
# Check if worktrunk (wt) is already installed
|
|
734
|
-
if command -v wt >/dev/null 2>&1; then
|
|
735
|
-
local wt_version
|
|
736
|
-
wt_version=$(wt --version 2>/dev/null | head -1 || echo "unknown")
|
|
737
|
-
print_success "Worktrunk already installed: $wt_version"
|
|
738
|
-
_check_worktrunk_shell_integration
|
|
739
|
-
return 0
|
|
740
|
-
fi
|
|
741
|
-
|
|
742
|
-
# Worktrunk not installed - offer to install
|
|
743
|
-
print_info "Worktrunk makes git worktrees as easy as branches"
|
|
744
|
-
echo " • wt switch feat - Switch/create worktree (with cd)"
|
|
745
|
-
echo " • wt list - List worktrees with CI status"
|
|
746
|
-
echo " • wt merge - Squash/rebase/merge + cleanup"
|
|
747
|
-
echo " • Hooks for automated setup (npm install, etc.)"
|
|
748
|
-
echo ""
|
|
749
|
-
echo " Note: aidevops also includes worktree-helper.sh as a fallback"
|
|
750
|
-
echo ""
|
|
751
|
-
|
|
752
|
-
local pkg_manager
|
|
753
|
-
pkg_manager=$(detect_package_manager)
|
|
754
|
-
|
|
755
|
-
if [[ "$pkg_manager" == "brew" ]]; then
|
|
756
|
-
_install_worktrunk_brew
|
|
757
|
-
elif command -v cargo >/dev/null 2>&1; then
|
|
758
|
-
_install_worktrunk_cargo
|
|
759
|
-
else
|
|
760
|
-
print_warning "Worktrunk not installed"
|
|
761
|
-
echo ""
|
|
762
|
-
echo " Install options:"
|
|
763
|
-
echo " macOS/Linux (Homebrew): brew install max-sixty/worktrunk/wt"
|
|
764
|
-
echo " Cargo: cargo install worktrunk"
|
|
765
|
-
echo " Windows: winget install max-sixty.worktrunk"
|
|
766
|
-
echo ""
|
|
767
|
-
echo " After install: wt config shell install"
|
|
768
|
-
echo ""
|
|
769
|
-
print_info "Fallback available: ~/.aidevops/agents/scripts/worktree-helper.sh"
|
|
770
|
-
fi
|
|
771
|
-
|
|
772
|
-
return 0
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
# Trigger OpenCode extension install in Zed via the zed:// URI scheme.
|
|
776
|
-
_install_opencode_ext_for_zed() {
|
|
777
|
-
local install_opencode_ext
|
|
778
|
-
setup_prompt install_opencode_ext "Install OpenCode extension for Zed? [Y/n]: " "Y"
|
|
779
|
-
if [[ "$install_opencode_ext" =~ ^[Yy]?$ ]]; then
|
|
780
|
-
print_info "Installing OpenCode extension..."
|
|
781
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
782
|
-
open "zed://extension/opencode" 2>/dev/null
|
|
783
|
-
print_success "OpenCode extension install triggered"
|
|
784
|
-
print_info "Zed will open and prompt to install the extension"
|
|
785
|
-
elif [[ "$(uname)" == "Linux" ]]; then
|
|
786
|
-
xdg-open "zed://extension/opencode" 2>/dev/null ||
|
|
787
|
-
print_info "Open Zed and install 'opencode' from Extensions (Cmd+Shift+X)"
|
|
788
|
-
fi
|
|
789
|
-
fi
|
|
790
|
-
return 0
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
# Install Tabby terminal on Linux (x86_64 only via packagecloud; ARM64 manual).
|
|
794
|
-
_install_tabby_linux() {
|
|
795
|
-
local arch
|
|
796
|
-
arch=$(uname -m)
|
|
797
|
-
# Tabby packagecloud repo only has x86_64 packages
|
|
798
|
-
# ARM64 (aarch64) must use .deb from GitHub releases or skip
|
|
799
|
-
if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
|
|
800
|
-
# Clean up stale Tabby packagecloud repo if it exists from a previous run
|
|
801
|
-
# (it causes apt-get update failures on ARM64)
|
|
802
|
-
if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
|
|
803
|
-
print_info "Removing stale Tabby packagecloud repo (not available for ARM64)..."
|
|
804
|
-
sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
|
|
805
|
-
sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
|
|
806
|
-
sudo apt-get update -qq 2>/dev/null || true
|
|
807
|
-
fi
|
|
808
|
-
print_warning "Tabby packages are not available for ARM64 Linux via package manager"
|
|
809
|
-
echo " Download ARM64 .deb from: https://github.com/Eugeny/tabby/releases/latest"
|
|
810
|
-
echo " Or skip Tabby - it's optional (a modern terminal emulator)"
|
|
811
|
-
return 0
|
|
812
|
-
fi
|
|
813
|
-
|
|
814
|
-
local pkg_manager
|
|
815
|
-
pkg_manager=$(detect_package_manager)
|
|
816
|
-
case "$pkg_manager" in
|
|
817
|
-
apt)
|
|
818
|
-
# Add packagecloud repo for Tabby (verified download, not piped to sudo)
|
|
819
|
-
# shellcheck disable=SC2034 # Read by verified_install() in setup.sh
|
|
820
|
-
VERIFIED_INSTALL_SUDO="true"
|
|
821
|
-
if verified_install "Tabby repository (apt)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.deb.sh"; then
|
|
822
|
-
if ! sudo apt-get install -y tabby-terminal; then
|
|
823
|
-
print_warning "Tabby package not found for this architecture"
|
|
824
|
-
echo " Download from: https://github.com/Eugeny/tabby/releases/latest"
|
|
825
|
-
fi
|
|
826
|
-
fi
|
|
827
|
-
;;
|
|
828
|
-
dnf | yum)
|
|
829
|
-
# shellcheck disable=SC2034 # Read by verified_install() in setup.sh
|
|
830
|
-
VERIFIED_INSTALL_SUDO="true"
|
|
831
|
-
if verified_install "Tabby repository (rpm)" "https://packagecloud.io/install/repositories/eugeny/tabby/script.rpm.sh"; then
|
|
832
|
-
if ! sudo "$pkg_manager" install -y tabby-terminal; then
|
|
833
|
-
print_warning "Tabby package not found for this architecture"
|
|
834
|
-
echo " Download from: https://github.com/Eugeny/tabby/releases/latest"
|
|
835
|
-
fi
|
|
836
|
-
fi
|
|
837
|
-
;;
|
|
838
|
-
pacman)
|
|
839
|
-
# AUR package
|
|
840
|
-
print_info "Tabby available in AUR as 'tabby-bin'"
|
|
841
|
-
echo " Install with: yay -S tabby-bin"
|
|
842
|
-
;;
|
|
843
|
-
*)
|
|
844
|
-
echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
|
|
845
|
-
;;
|
|
846
|
-
esac
|
|
847
|
-
return 0
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
# Offer and perform Tabby terminal installation.
|
|
851
|
-
_install_tabby() {
|
|
852
|
-
local install_tabby
|
|
853
|
-
setup_prompt install_tabby "Install Tabby terminal? [Y/n]: " "Y"
|
|
854
|
-
|
|
855
|
-
if [[ "$install_tabby" =~ ^[Yy]?$ ]]; then
|
|
856
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
857
|
-
if command -v brew >/dev/null 2>&1; then
|
|
858
|
-
if run_with_spinner "Installing Tabby" brew install --cask tabby; then
|
|
859
|
-
: # Success message handled by spinner
|
|
860
|
-
else
|
|
861
|
-
print_warning "Failed to install Tabby via Homebrew"
|
|
862
|
-
echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
|
|
863
|
-
fi
|
|
864
|
-
else
|
|
865
|
-
print_warning "Homebrew not found"
|
|
866
|
-
echo " Download manually: https://github.com/Eugeny/tabby/releases/latest"
|
|
867
|
-
fi
|
|
868
|
-
elif [[ "$(uname)" == "Linux" ]]; then
|
|
869
|
-
_install_tabby_linux
|
|
870
|
-
fi
|
|
871
|
-
else
|
|
872
|
-
print_info "Skipped Tabby installation"
|
|
873
|
-
fi
|
|
874
|
-
return 0
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
# Offer and perform Zed editor installation, then optionally install OpenCode extension.
|
|
878
|
-
_install_zed_and_opencode_ext() {
|
|
879
|
-
local install_zed
|
|
880
|
-
setup_prompt install_zed "Install Zed editor? [Y/n]: " "Y"
|
|
881
|
-
|
|
882
|
-
if [[ "$install_zed" =~ ^[Yy]?$ ]]; then
|
|
883
|
-
local zed_installed=false
|
|
884
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
885
|
-
if command -v brew >/dev/null 2>&1; then
|
|
886
|
-
if run_with_spinner "Installing Zed" brew install --cask zed; then
|
|
887
|
-
zed_installed=true
|
|
888
|
-
else
|
|
889
|
-
print_warning "Failed to install Zed via Homebrew"
|
|
890
|
-
echo " Download manually: https://zed.dev/download"
|
|
891
|
-
fi
|
|
892
|
-
else
|
|
893
|
-
print_warning "Homebrew not found"
|
|
894
|
-
echo " Download manually: https://zed.dev/download"
|
|
895
|
-
fi
|
|
896
|
-
elif [[ "$(uname)" == "Linux" ]]; then
|
|
897
|
-
# Zed provides an install script for Linux (verified download)
|
|
898
|
-
# shellcheck disable=SC2034 # Read by verified_install() in setup.sh
|
|
899
|
-
VERIFIED_INSTALL_SHELL="sh"
|
|
900
|
-
if verified_install "Zed" "https://zed.dev/install.sh"; then
|
|
901
|
-
zed_installed=true
|
|
902
|
-
else
|
|
903
|
-
print_warning "Failed to install Zed"
|
|
904
|
-
echo " See: https://zed.dev/docs/linux"
|
|
905
|
-
fi
|
|
906
|
-
fi
|
|
907
|
-
|
|
908
|
-
if [[ "$zed_installed" == "true" ]]; then
|
|
909
|
-
_install_opencode_ext_for_zed
|
|
910
|
-
fi
|
|
911
|
-
else
|
|
912
|
-
print_info "Skipped Zed installation"
|
|
913
|
-
fi
|
|
914
|
-
return 0
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
# Check for OpenCode extension in an existing Zed installation and offer to install.
|
|
918
|
-
_check_opencode_ext_existing_zed() {
|
|
919
|
-
local zed_extensions_dir=""
|
|
920
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
921
|
-
zed_extensions_dir="$HOME/Library/Application Support/Zed/extensions/installed"
|
|
922
|
-
elif [[ "$(uname)" == "Linux" ]]; then
|
|
923
|
-
zed_extensions_dir="$HOME/.local/share/zed/extensions/installed"
|
|
924
|
-
fi
|
|
925
|
-
|
|
926
|
-
if [[ -d "$zed_extensions_dir" ]]; then
|
|
927
|
-
if [[ ! -d "$zed_extensions_dir/opencode" ]]; then
|
|
928
|
-
_install_opencode_ext_for_zed
|
|
929
|
-
else
|
|
930
|
-
print_success "OpenCode extension already installed in Zed"
|
|
931
|
-
fi
|
|
932
|
-
fi
|
|
933
|
-
return 0
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
setup_recommended_tools() {
|
|
937
|
-
print_info "Checking recommended development tools..."
|
|
938
|
-
|
|
939
|
-
local missing_tools=()
|
|
940
|
-
local missing_names=()
|
|
941
|
-
|
|
942
|
-
# Check for Tabby terminal
|
|
943
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
944
|
-
# macOS - check Applications folder
|
|
945
|
-
if [[ ! -d "/Applications/Tabby.app" ]]; then
|
|
946
|
-
missing_tools+=("tabby")
|
|
947
|
-
missing_names+=("Tabby (modern terminal)")
|
|
948
|
-
else
|
|
949
|
-
print_success "Tabby terminal found"
|
|
950
|
-
fi
|
|
951
|
-
elif [[ "$(uname)" == "Linux" ]]; then
|
|
952
|
-
# Linux - check if tabby command exists
|
|
953
|
-
if ! command -v tabby >/dev/null 2>&1; then
|
|
954
|
-
missing_tools+=("tabby")
|
|
955
|
-
missing_names+=("Tabby (modern terminal)")
|
|
956
|
-
else
|
|
957
|
-
print_success "Tabby terminal found"
|
|
958
|
-
fi
|
|
959
|
-
fi
|
|
960
|
-
|
|
961
|
-
# Check for Zed editor
|
|
962
|
-
local zed_exists=false
|
|
963
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
964
|
-
# macOS - check Applications folder
|
|
965
|
-
if [[ ! -d "/Applications/Zed.app" ]]; then
|
|
966
|
-
missing_tools+=("zed")
|
|
967
|
-
missing_names+=("Zed (AI-native editor)")
|
|
968
|
-
else
|
|
969
|
-
print_success "Zed editor found"
|
|
970
|
-
zed_exists=true
|
|
971
|
-
fi
|
|
972
|
-
elif [[ "$(uname)" == "Linux" ]]; then
|
|
973
|
-
# Linux - check if zed command exists
|
|
974
|
-
if ! command -v zed >/dev/null 2>&1; then
|
|
975
|
-
missing_tools+=("zed")
|
|
976
|
-
missing_names+=("Zed (AI-native editor)")
|
|
977
|
-
else
|
|
978
|
-
print_success "Zed editor found"
|
|
979
|
-
zed_exists=true
|
|
980
|
-
fi
|
|
981
|
-
fi
|
|
982
|
-
|
|
983
|
-
# Check for OpenCode extension in existing Zed installation
|
|
984
|
-
if [[ "$zed_exists" == "true" ]]; then
|
|
985
|
-
_check_opencode_ext_existing_zed
|
|
986
|
-
fi
|
|
987
|
-
|
|
988
|
-
# Offer to install missing tools
|
|
989
|
-
if [[ ${#missing_tools[@]} -gt 0 ]]; then
|
|
990
|
-
print_warning "Missing recommended tools: ${missing_names[*]}"
|
|
991
|
-
echo " Tabby - Modern terminal with profiles, SSH manager, split panes"
|
|
992
|
-
echo " Zed - High-performance AI-native code editor"
|
|
993
|
-
echo ""
|
|
994
|
-
|
|
995
|
-
# Install Tabby if missing
|
|
996
|
-
if [[ " ${missing_tools[*]} " =~ " tabby " ]]; then
|
|
997
|
-
_install_tabby
|
|
998
|
-
fi
|
|
999
|
-
|
|
1000
|
-
# Install Zed if missing
|
|
1001
|
-
if [[ " ${missing_tools[*]} " =~ " zed " ]]; then
|
|
1002
|
-
_install_zed_and_opencode_ext
|
|
1003
|
-
fi
|
|
1004
|
-
else
|
|
1005
|
-
print_success "All recommended tools installed!"
|
|
1006
|
-
fi
|
|
1007
|
-
|
|
1008
|
-
# Check for Cursor CLI (agent) — independent of the missing_tools flow
|
|
1009
|
-
# since it uses a curl installer, not brew
|
|
1010
|
-
setup_cursor_cli
|
|
1011
|
-
|
|
1012
|
-
return 0
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
setup_cursor_cli() {
|
|
1016
|
-
print_info "Checking Cursor CLI (agent)..."
|
|
1017
|
-
|
|
1018
|
-
if command -v agent >/dev/null 2>&1; then
|
|
1019
|
-
local cursor_version
|
|
1020
|
-
cursor_version=$(agent --version 2>/dev/null || echo "unknown")
|
|
1021
|
-
print_success "Cursor CLI found: $cursor_version"
|
|
1022
|
-
return 0
|
|
1023
|
-
fi
|
|
1024
|
-
|
|
1025
|
-
# Check ~/.local/bin specifically (may not be in PATH yet)
|
|
1026
|
-
if [[ -x "$HOME/.local/bin/agent" ]]; then
|
|
1027
|
-
local cursor_version
|
|
1028
|
-
cursor_version=$("$HOME/.local/bin/agent" --version 2>/dev/null || echo "unknown")
|
|
1029
|
-
print_success "Cursor CLI found at ~/.local/bin/agent: $cursor_version"
|
|
1030
|
-
print_info "Ensure ~/.local/bin is in your PATH"
|
|
1031
|
-
return 0
|
|
1032
|
-
fi
|
|
1033
|
-
|
|
1034
|
-
echo " Cursor CLI provides access to Cursor's AI models (including Composer 2)"
|
|
1035
|
-
echo " from the terminal. Also usable as an OpenCode provider via the"
|
|
1036
|
-
echo " opencode-cursor plugin for OAuth-based model access."
|
|
1037
|
-
echo ""
|
|
1038
|
-
|
|
1039
|
-
local install_cursor
|
|
1040
|
-
setup_prompt install_cursor "Install Cursor CLI? [Y/n]: " "Y"
|
|
1041
|
-
|
|
1042
|
-
if [[ "$install_cursor" =~ ^[Yy]?$ ]]; then
|
|
1043
|
-
print_info "Installing Cursor CLI..."
|
|
1044
|
-
if verified_install "Cursor CLI" "https://cursor.com/install"; then
|
|
1045
|
-
# Ensure ~/.local/bin is in PATH for this session
|
|
1046
|
-
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
|
1047
|
-
export PATH="$HOME/.local/bin:$PATH"
|
|
1048
|
-
print_info "Added ~/.local/bin to PATH for this session"
|
|
1049
|
-
fi
|
|
1050
|
-
print_success "Cursor CLI installed"
|
|
1051
|
-
echo ""
|
|
1052
|
-
echo " Next steps:"
|
|
1053
|
-
echo " agent login # Authenticate with your Cursor account"
|
|
1054
|
-
echo " agent models # List available models"
|
|
1055
|
-
echo " agent status # Check auth status"
|
|
1056
|
-
else
|
|
1057
|
-
print_warning "Failed to install Cursor CLI"
|
|
1058
|
-
echo " Install manually: curl https://cursor.com/install -fsS | bash"
|
|
1059
|
-
fi
|
|
1060
|
-
else
|
|
1061
|
-
print_info "Skipped Cursor CLI installation"
|
|
1062
|
-
echo " Install later: curl https://cursor.com/install -fsS | bash"
|
|
1063
|
-
fi
|
|
1064
|
-
|
|
1065
|
-
return 0
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
setup_minisim() {
|
|
1069
|
-
# Only available on macOS
|
|
1070
|
-
if [[ "$(uname)" != "Darwin" ]]; then
|
|
1071
|
-
return 0
|
|
1072
|
-
fi
|
|
1073
|
-
|
|
1074
|
-
print_info "Setting up MiniSim (iOS/Android emulator launcher)..."
|
|
1075
|
-
|
|
1076
|
-
# Check if MiniSim is already installed
|
|
1077
|
-
if [[ -d "/Applications/MiniSim.app" ]]; then
|
|
1078
|
-
print_success "MiniSim already installed"
|
|
1079
|
-
print_info "Global shortcut: Option + Shift + E"
|
|
1080
|
-
return 0
|
|
1081
|
-
fi
|
|
1082
|
-
|
|
1083
|
-
# Check if Xcode or Android Studio is installed (MiniSim needs at least one)
|
|
1084
|
-
local has_xcode=false
|
|
1085
|
-
local has_android=false
|
|
1086
|
-
|
|
1087
|
-
if command -v xcrun >/dev/null 2>&1 && xcrun simctl list devices >/dev/null 2>&1; then
|
|
1088
|
-
has_xcode=true
|
|
1089
|
-
fi
|
|
1090
|
-
|
|
1091
|
-
if [[ -n "${ANDROID_HOME:-}" ]] || [[ -n "${ANDROID_SDK_ROOT:-}" ]] || [[ -d "$HOME/Library/Android/sdk" ]]; then
|
|
1092
|
-
has_android=true
|
|
1093
|
-
fi
|
|
1094
|
-
|
|
1095
|
-
if [[ "$has_xcode" == "false" && "$has_android" == "false" ]]; then
|
|
1096
|
-
print_info "MiniSim requires Xcode (iOS) or Android Studio (Android)"
|
|
1097
|
-
print_info "Install one of these first, then re-run setup to install MiniSim"
|
|
1098
|
-
return 0
|
|
1099
|
-
fi
|
|
1100
|
-
|
|
1101
|
-
# Show what's available
|
|
1102
|
-
local available_for=""
|
|
1103
|
-
if [[ "$has_xcode" == "true" ]]; then
|
|
1104
|
-
available_for="iOS simulators"
|
|
1105
|
-
fi
|
|
1106
|
-
if [[ "$has_android" == "true" ]]; then
|
|
1107
|
-
if [[ -n "$available_for" ]]; then
|
|
1108
|
-
available_for="$available_for and Android emulators"
|
|
1109
|
-
else
|
|
1110
|
-
available_for="Android emulators"
|
|
1111
|
-
fi
|
|
1112
|
-
fi
|
|
1113
|
-
|
|
1114
|
-
print_info "MiniSim is a menu bar app for launching $available_for"
|
|
1115
|
-
echo " Features:"
|
|
1116
|
-
echo " - Global shortcut: Option + Shift + E"
|
|
1117
|
-
echo " - Launch/manage iOS simulators and Android emulators"
|
|
1118
|
-
echo " - Copy device UDID/ADB ID"
|
|
1119
|
-
echo " - Cold boot Android emulators"
|
|
1120
|
-
echo " - Run Android emulators without audio (saves Bluetooth battery)"
|
|
1121
|
-
echo ""
|
|
1122
|
-
|
|
1123
|
-
# Check if Homebrew is available
|
|
1124
|
-
if ! command -v brew >/dev/null 2>&1; then
|
|
1125
|
-
print_warning "Homebrew not found - cannot install MiniSim automatically"
|
|
1126
|
-
echo " Install manually: https://github.com/okwasniewski/MiniSim/releases"
|
|
1127
|
-
return 0
|
|
1128
|
-
fi
|
|
1129
|
-
|
|
1130
|
-
local install_minisim
|
|
1131
|
-
setup_prompt install_minisim "Install MiniSim? [Y/n]: " "Y"
|
|
1132
|
-
|
|
1133
|
-
if [[ "$install_minisim" =~ ^[Yy]?$ ]]; then
|
|
1134
|
-
if run_with_spinner "Installing MiniSim" brew install --cask minisim; then
|
|
1135
|
-
print_info "Global shortcut: Option + Shift + E"
|
|
1136
|
-
print_info "Documentation: ~/.aidevops/agents/tools/mobile/minisim.md"
|
|
1137
|
-
else
|
|
1138
|
-
print_warning "Failed to install MiniSim via Homebrew"
|
|
1139
|
-
echo " Install manually: https://github.com/okwasniewski/MiniSim/releases"
|
|
1140
|
-
fi
|
|
1141
|
-
else
|
|
1142
|
-
print_info "Skipped MiniSim installation"
|
|
1143
|
-
print_info "Install later: brew install --cask minisim"
|
|
1144
|
-
fi
|
|
1145
|
-
|
|
1146
|
-
return 0
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
setup_claudebar() {
|
|
1150
|
-
local claudebar_release_url="https://github.com/tddworks/ClaudeBar/releases/latest"
|
|
1151
|
-
# Only available on macOS (native Swift menu bar app)
|
|
1152
|
-
if [[ "$(uname)" != "Darwin" ]]; then
|
|
1153
|
-
return 0
|
|
1154
|
-
fi
|
|
1155
|
-
|
|
1156
|
-
print_info "Setting up ClaudeBar (AI quota monitor)..."
|
|
1157
|
-
|
|
1158
|
-
# Check if ClaudeBar is already installed
|
|
1159
|
-
if [[ -d "/Applications/ClaudeBar.app" ]]; then
|
|
1160
|
-
print_success "ClaudeBar already installed"
|
|
1161
|
-
return 0
|
|
1162
|
-
fi
|
|
1163
|
-
|
|
1164
|
-
# Check if Homebrew is available (required for cask install)
|
|
1165
|
-
if ! command -v brew >/dev/null 2>&1; then
|
|
1166
|
-
print_warning "Homebrew not found - cannot install ClaudeBar automatically"
|
|
1167
|
-
echo " Download manually: $claudebar_release_url"
|
|
1168
|
-
return 0
|
|
1169
|
-
fi
|
|
1170
|
-
|
|
1171
|
-
print_info "ClaudeBar monitors AI coding assistant usage quotas in your menu bar"
|
|
1172
|
-
echo " Supports: Claude, Codex, Gemini, Copilot, Antigravity, Kimi, Kiro, Amp"
|
|
1173
|
-
echo " Features: real-time quota tracking, status notifications, multiple themes"
|
|
1174
|
-
echo " Requires: macOS 15+, CLI tools for providers you want to monitor"
|
|
1175
|
-
echo ""
|
|
1176
|
-
|
|
1177
|
-
local install_claudebar
|
|
1178
|
-
setup_prompt install_claudebar "Install ClaudeBar? [Y/n]: " "Y"
|
|
1179
|
-
|
|
1180
|
-
if [[ "$install_claudebar" =~ ^[Yy]?$ ]]; then
|
|
1181
|
-
if run_with_spinner "Installing ClaudeBar" brew install --cask claudebar; then
|
|
1182
|
-
print_success "ClaudeBar installed"
|
|
1183
|
-
print_info "Launch from Applications or Spotlight to start monitoring quotas"
|
|
1184
|
-
else
|
|
1185
|
-
print_warning "Failed to install ClaudeBar via Homebrew"
|
|
1186
|
-
echo " Download manually: $claudebar_release_url"
|
|
1187
|
-
fi
|
|
1188
|
-
else
|
|
1189
|
-
print_info "Skipped ClaudeBar installation"
|
|
1190
|
-
print_info "Install later: brew install --cask claudebar"
|
|
1191
|
-
fi
|
|
1192
|
-
|
|
1193
|
-
return 0
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
setup_ssh_key() {
|
|
1197
|
-
print_info "Checking SSH key setup..."
|
|
1198
|
-
|
|
1199
|
-
if [[ ! -f ~/.ssh/id_ed25519 ]]; then
|
|
1200
|
-
print_warning "Ed25519 SSH key not found"
|
|
1201
|
-
|
|
1202
|
-
# SSH key generation requires email input — skip in non-interactive mode
|
|
1203
|
-
if [[ "${NON_INTERACTIVE:-false}" == "true" ]] || [[ ! -t 0 ]]; then
|
|
1204
|
-
print_info "Skipping SSH key generation (non-interactive mode)"
|
|
1205
|
-
return 0
|
|
1206
|
-
fi
|
|
1207
|
-
|
|
1208
|
-
local generate_key
|
|
1209
|
-
setup_prompt generate_key "Generate new Ed25519 SSH key? [Y/n]: " "Y"
|
|
1210
|
-
|
|
1211
|
-
if [[ "$generate_key" =~ ^[Yy]?$ ]]; then
|
|
1212
|
-
local email
|
|
1213
|
-
setup_prompt email "Enter your email address: " ""
|
|
1214
|
-
if [[ -z "$email" ]]; then
|
|
1215
|
-
print_warning "No email provided — skipping SSH key generation"
|
|
1216
|
-
return 0
|
|
1217
|
-
fi
|
|
1218
|
-
install -d -m 700 ~/.ssh
|
|
1219
|
-
ssh-keygen -t ed25519 -C "$email" -f ~/.ssh/id_ed25519
|
|
1220
|
-
print_success "SSH key generated"
|
|
1221
|
-
else
|
|
1222
|
-
print_info "Skipping SSH key generation"
|
|
1223
|
-
fi
|
|
1224
|
-
else
|
|
1225
|
-
print_success "Ed25519 SSH key found"
|
|
1226
|
-
fi
|
|
1227
|
-
return 0
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
# Check installed Python version against latest stable available from package manager.
|
|
1231
|
-
# Warns if an upgrade is available but never auto-upgrades (GH#5237).
|
|
1232
|
-
# Works on macOS (Homebrew) and Linux (apt/dnf).
|
|
1233
|
-
# Named check_python_upgrade_available() to avoid collision with the shared
|
|
1234
|
-
# check_python_version() in _common.sh (which validates minimum required version).
|
|
1235
|
-
check_python_upgrade_available() {
|
|
1236
|
-
print_info "Checking Python version..."
|
|
1237
|
-
|
|
1238
|
-
# 1. Check currently installed Python
|
|
1239
|
-
local python3_bin
|
|
1240
|
-
if ! python3_bin=$(find_python3); then
|
|
1241
|
-
print_warning "Python 3 not found"
|
|
1242
|
-
echo ""
|
|
1243
|
-
echo " Install options:"
|
|
1244
|
-
if [[ "$PLATFORM_MACOS" == "true" ]]; then
|
|
1245
|
-
echo " brew install python3"
|
|
1246
|
-
elif command -v apt-get >/dev/null 2>&1; then
|
|
1247
|
-
echo " sudo apt install python3"
|
|
1248
|
-
elif command -v dnf >/dev/null 2>&1; then
|
|
1249
|
-
echo " sudo dnf install python3"
|
|
1250
|
-
else
|
|
1251
|
-
echo " Install Python 3 via your system package manager"
|
|
1252
|
-
fi
|
|
1253
|
-
echo ""
|
|
1254
|
-
return 0
|
|
1255
|
-
fi
|
|
1256
|
-
|
|
1257
|
-
local installed_version
|
|
1258
|
-
installed_version=$("$python3_bin" --version 2>&1 | cut -d' ' -f2)
|
|
1259
|
-
local installed_major installed_minor
|
|
1260
|
-
installed_major=$(echo "$installed_version" | cut -d. -f1)
|
|
1261
|
-
installed_minor=$(echo "$installed_version" | cut -d. -f2)
|
|
1262
|
-
|
|
1263
|
-
# 2. Determine latest stable version from package manager
|
|
1264
|
-
local latest_version=""
|
|
1265
|
-
|
|
1266
|
-
if [[ "$PLATFORM_MACOS" == "true" ]] && command -v brew >/dev/null 2>&1; then
|
|
1267
|
-
# Homebrew: `brew info python3` outputs "python@3.X: 3.X.Y" on the first line
|
|
1268
|
-
latest_version=$(brew info --json=v2 python3 2>/dev/null |
|
|
1269
|
-
python3 -c "import sys,json; d=json.load(sys.stdin); print(d['formulae'][0]['versions']['stable'])" 2>/dev/null) || latest_version=""
|
|
1270
|
-
elif command -v apt-cache >/dev/null 2>&1; then
|
|
1271
|
-
# Debian/Ubuntu: get candidate version from apt-cache
|
|
1272
|
-
latest_version=$(apt-cache policy python3 2>/dev/null |
|
|
1273
|
-
awk '/Candidate:/{print $2}' |
|
|
1274
|
-
grep -oE '[0-9]+\.[0-9]+\.[0-9]+') || latest_version=""
|
|
1275
|
-
elif command -v dnf >/dev/null 2>&1; then
|
|
1276
|
-
# Fedora/RHEL: get available version from dnf
|
|
1277
|
-
latest_version=$(dnf info python3 2>/dev/null |
|
|
1278
|
-
awk '/^Version/{print $3}') || latest_version=""
|
|
1279
|
-
fi
|
|
1280
|
-
|
|
1281
|
-
# 3. Compare versions and advise
|
|
1282
|
-
if [[ -z "$latest_version" ]]; then
|
|
1283
|
-
# Could not determine latest — just report installed version
|
|
1284
|
-
print_success "Python $installed_version found"
|
|
1285
|
-
return 0
|
|
1286
|
-
fi
|
|
1287
|
-
|
|
1288
|
-
local latest_major latest_minor
|
|
1289
|
-
latest_major=$(echo "$latest_version" | cut -d. -f1)
|
|
1290
|
-
latest_minor=$(echo "$latest_version" | cut -d. -f2)
|
|
1291
|
-
|
|
1292
|
-
# Compare major.minor (patch differences are not worth warning about)
|
|
1293
|
-
if [[ "$installed_major" -lt "$latest_major" ]] ||
|
|
1294
|
-
{ [[ "$installed_major" -eq "$latest_major" ]] && [[ "$installed_minor" -lt "$latest_minor" ]]; }; then
|
|
1295
|
-
print_warning "Python $installed_version installed, but $latest_version is available"
|
|
1296
|
-
echo ""
|
|
1297
|
-
echo " Some tools and skills require Python 3.10+."
|
|
1298
|
-
echo " Upgrade is recommended but not required."
|
|
1299
|
-
echo ""
|
|
1300
|
-
if [[ "$PLATFORM_MACOS" == "true" ]]; then
|
|
1301
|
-
echo " Upgrade command:"
|
|
1302
|
-
echo " brew upgrade python3"
|
|
1303
|
-
elif command -v apt-get >/dev/null 2>&1; then
|
|
1304
|
-
echo " Upgrade command:"
|
|
1305
|
-
echo " sudo apt update && sudo apt install python3"
|
|
1306
|
-
elif command -v dnf >/dev/null 2>&1; then
|
|
1307
|
-
echo " Upgrade command:"
|
|
1308
|
-
echo " sudo dnf upgrade python3"
|
|
1309
|
-
fi
|
|
1310
|
-
echo ""
|
|
1311
|
-
else
|
|
1312
|
-
print_success "Python $installed_version found (latest stable: $latest_version)"
|
|
1313
|
-
fi
|
|
1314
|
-
|
|
1315
|
-
return 0
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
setup_python_env() {
|
|
1319
|
-
print_info "Setting up Python environment for DSPy..."
|
|
1320
|
-
|
|
1321
|
-
# Check if Python 3 is available
|
|
1322
|
-
local python3_bin
|
|
1323
|
-
if ! python3_bin=$(find_python3); then
|
|
1324
|
-
print_warning "Python 3 not found - DSPy setup skipped"
|
|
1325
|
-
print_info "Install Python 3.8+ to enable DSPy integration"
|
|
1326
|
-
return
|
|
1327
|
-
fi
|
|
1328
|
-
|
|
1329
|
-
local python_version
|
|
1330
|
-
python_version=$("$python3_bin" --version | cut -d' ' -f2 | cut -d'.' -f1-2)
|
|
1331
|
-
local version_check
|
|
1332
|
-
version_check=$("$python3_bin" -c "import sys; print(1 if sys.version_info >= (3, 8) else 0)")
|
|
1333
|
-
|
|
1334
|
-
if [[ "$version_check" != "1" ]]; then
|
|
1335
|
-
print_warning "Python 3.8+ required for DSPy, found $python_version - DSPy setup skipped"
|
|
1336
|
-
return
|
|
1337
|
-
fi
|
|
1338
|
-
|
|
1339
|
-
# Create Python virtual environment
|
|
1340
|
-
if [[ ! -d "python-env/dspy-env" ]] || [[ ! -f "python-env/dspy-env/bin/activate" ]]; then
|
|
1341
|
-
print_info "Creating Python virtual environment for DSPy..."
|
|
1342
|
-
mkdir -p python-env
|
|
1343
|
-
# Remove corrupted venv if directory exists but activate script is missing
|
|
1344
|
-
if [[ -d "python-env/dspy-env" ]] && [[ ! -f "python-env/dspy-env/bin/activate" ]]; then
|
|
1345
|
-
rm -rf python-env/dspy-env
|
|
1346
|
-
fi
|
|
1347
|
-
if "$python3_bin" -m venv python-env/dspy-env; then
|
|
1348
|
-
print_success "Python virtual environment created"
|
|
1349
|
-
else
|
|
1350
|
-
print_warning "Failed to create Python virtual environment - DSPy setup skipped"
|
|
1351
|
-
return
|
|
1352
|
-
fi
|
|
1353
|
-
else
|
|
1354
|
-
print_info "Python virtual environment already exists"
|
|
1355
|
-
fi
|
|
1356
|
-
|
|
1357
|
-
# Install DSPy dependencies
|
|
1358
|
-
print_info "Installing DSPy dependencies..."
|
|
1359
|
-
# shellcheck source=/dev/null
|
|
1360
|
-
if [[ -f "python-env/dspy-env/bin/activate" ]]; then
|
|
1361
|
-
source python-env/dspy-env/bin/activate
|
|
1362
|
-
else
|
|
1363
|
-
print_warning "Python venv activate script not found - DSPy setup skipped"
|
|
1364
|
-
return
|
|
1365
|
-
fi
|
|
1366
|
-
pip install --upgrade pip >/dev/null 2>&1
|
|
1367
|
-
|
|
1368
|
-
if run_with_spinner "Installing DSPy dependencies" pip install -r requirements.txt; then
|
|
1369
|
-
: # Success message handled by spinner
|
|
1370
|
-
else
|
|
1371
|
-
print_info "Check requirements.txt or run manually:"
|
|
1372
|
-
print_info " source python-env/dspy-env/bin/activate && pip install -r requirements.txt"
|
|
1373
|
-
fi
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
setup_nodejs_env() {
|
|
1377
|
-
print_info "Setting up Node.js environment for DSPyGround..."
|
|
1378
|
-
|
|
1379
|
-
# Check if Node.js is available
|
|
1380
|
-
if ! command -v node &>/dev/null; then
|
|
1381
|
-
print_warning "Node.js not found - DSPyGround setup skipped"
|
|
1382
|
-
print_info "Install Node.js 18+ to enable DSPyGround integration"
|
|
1383
|
-
return
|
|
1384
|
-
fi
|
|
1385
|
-
|
|
1386
|
-
local node_version
|
|
1387
|
-
node_version=$(node --version 2>/dev/null | cut -d'v' -f2 | cut -d'.' -f1)
|
|
1388
|
-
if [[ -z "$node_version" ]] || ! [[ "$node_version" =~ ^[0-9]+$ ]]; then
|
|
1389
|
-
print_warning "Could not determine Node.js version - DSPyGround setup skipped"
|
|
1390
|
-
return
|
|
1391
|
-
fi
|
|
1392
|
-
if [[ "$node_version" -lt 18 ]]; then
|
|
1393
|
-
print_warning "Node.js 18+ required for DSPyGround, found v$node_version - DSPyGround setup skipped"
|
|
1394
|
-
return
|
|
1395
|
-
fi
|
|
1396
|
-
|
|
1397
|
-
# Check if npm is available
|
|
1398
|
-
if ! command -v npm &>/dev/null; then
|
|
1399
|
-
print_warning "npm not found - DSPyGround setup skipped"
|
|
1400
|
-
return
|
|
1401
|
-
fi
|
|
1402
|
-
|
|
1403
|
-
# Install DSPyGround globally if not already installed
|
|
1404
|
-
if ! command -v dspyground &>/dev/null; then
|
|
1405
|
-
if run_with_spinner "Installing DSPyGround" npm_global_install dspyground; then
|
|
1406
|
-
: # Success message handled by spinner
|
|
1407
|
-
else
|
|
1408
|
-
print_warning "Try manually: sudo npm install -g dspyground"
|
|
1409
|
-
fi
|
|
1410
|
-
else
|
|
1411
|
-
print_success "DSPyGround already installed"
|
|
1412
|
-
fi
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
# Install Node.js via apt, preferring NodeSource LTS over the distro package.
|
|
1416
|
-
_install_nodejs_apt() {
|
|
1417
|
-
# Clean up stale Tabby packagecloud repo if present (causes apt-get update failures)
|
|
1418
|
-
if [[ -f /etc/apt/sources.list.d/eugeny_tabby.list ]]; then
|
|
1419
|
-
local arch
|
|
1420
|
-
arch=$(uname -m)
|
|
1421
|
-
if [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
|
|
1422
|
-
print_info "Removing stale Tabby repo (not available for ARM64)..."
|
|
1423
|
-
sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.list
|
|
1424
|
-
sudo rm -f /etc/apt/sources.list.d/eugeny_tabby.sources
|
|
1425
|
-
fi
|
|
1426
|
-
fi
|
|
1427
|
-
|
|
1428
|
-
# Use NodeSource for a recent version (apt default may be old)
|
|
1429
|
-
print_info "Installing Node.js (via NodeSource for latest LTS)..."
|
|
1430
|
-
if command -v curl >/dev/null 2>&1; then
|
|
1431
|
-
# shellcheck disable=SC2034 # Read by verified_install() in setup.sh
|
|
1432
|
-
VERIFIED_INSTALL_SUDO="true"
|
|
1433
|
-
if verified_install "NodeSource repository" "https://deb.nodesource.com/setup_22.x"; then
|
|
1434
|
-
# Install nodejs (NodeSource bundles npm, but distro fallback may not)
|
|
1435
|
-
# Include npm explicitly in case NodeSource setup failed silently
|
|
1436
|
-
# and apt falls back to the distro nodejs package (which lacks npm)
|
|
1437
|
-
if sudo apt-get install -y nodejs npm 2>/dev/null || sudo apt-get install -y nodejs; then
|
|
1438
|
-
print_success "Node.js installed: $(node --version)"
|
|
1439
|
-
else
|
|
1440
|
-
print_warning "Node.js installation failed"
|
|
1441
|
-
fi
|
|
1442
|
-
else
|
|
1443
|
-
# Fallback to distro package
|
|
1444
|
-
print_info "Falling back to distro Node.js package..."
|
|
1445
|
-
if sudo apt-get install -y nodejs npm; then
|
|
1446
|
-
print_success "Node.js installed: $(node --version)"
|
|
1447
|
-
else
|
|
1448
|
-
print_warning "Node.js installation failed"
|
|
1449
|
-
fi
|
|
1450
|
-
fi
|
|
1451
|
-
else
|
|
1452
|
-
if sudo apt-get install -y nodejs npm; then
|
|
1453
|
-
print_success "Node.js installed: $(node --version)"
|
|
1454
|
-
else
|
|
1455
|
-
print_warning "Node.js installation failed"
|
|
1456
|
-
fi
|
|
1457
|
-
fi
|
|
1458
|
-
return 0
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
# Ensure npm is present when Node.js is already installed (distro packages may omit it).
|
|
1462
|
-
_ensure_npm_installed() {
|
|
1463
|
-
if command -v npm >/dev/null 2>&1; then
|
|
1464
|
-
return 0
|
|
1465
|
-
fi
|
|
1466
|
-
print_info "npm not found (distro nodejs package may omit it) — installing..."
|
|
1467
|
-
local pkg_manager
|
|
1468
|
-
pkg_manager=$(detect_package_manager)
|
|
1469
|
-
case "$pkg_manager" in
|
|
1470
|
-
apt) sudo apt-get install -y npm 2>/dev/null || print_warning "Failed to install npm via apt" ;;
|
|
1471
|
-
dnf | yum) sudo "$pkg_manager" install -y npm 2>/dev/null || print_warning "Failed to install npm via $pkg_manager" ;;
|
|
1472
|
-
brew) brew install npm 2>/dev/null || print_warning "Failed to install npm via brew" ;;
|
|
1473
|
-
*) print_warning "Cannot auto-install npm — install manually" ;;
|
|
1474
|
-
esac
|
|
1475
|
-
return 0
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
setup_nodejs() {
|
|
1479
|
-
# Check if Node.js is already installed
|
|
1480
|
-
if command -v node >/dev/null 2>&1; then
|
|
1481
|
-
local node_version
|
|
1482
|
-
node_version=$(node --version 2>/dev/null || echo "unknown")
|
|
1483
|
-
print_success "Node.js already installed: $node_version"
|
|
1484
|
-
_ensure_npm_installed
|
|
1485
|
-
return 0
|
|
1486
|
-
fi
|
|
1487
|
-
|
|
1488
|
-
print_info "Node.js is required for OpenCode, MCP servers, and many tools"
|
|
1489
|
-
|
|
1490
|
-
local pkg_manager
|
|
1491
|
-
pkg_manager=$(detect_package_manager)
|
|
1492
|
-
|
|
1493
|
-
local install_node
|
|
1494
|
-
case "$pkg_manager" in
|
|
1495
|
-
brew)
|
|
1496
|
-
setup_prompt install_node "Install Node.js via Homebrew? [Y/n]: " "Y"
|
|
1497
|
-
if [[ "$install_node" =~ ^[Yy]?$ ]]; then
|
|
1498
|
-
if run_with_spinner "Installing Node.js" brew install node; then
|
|
1499
|
-
print_success "Node.js installed: $(node --version)"
|
|
1500
|
-
else
|
|
1501
|
-
print_warning "Node.js installation failed"
|
|
1502
|
-
fi
|
|
1503
|
-
fi
|
|
1504
|
-
;;
|
|
1505
|
-
apt)
|
|
1506
|
-
setup_prompt install_node "Install Node.js via apt? [Y/n]: " "Y"
|
|
1507
|
-
if [[ "$install_node" =~ ^[Yy]?$ ]]; then
|
|
1508
|
-
_install_nodejs_apt
|
|
1509
|
-
fi
|
|
1510
|
-
;;
|
|
1511
|
-
dnf | yum)
|
|
1512
|
-
setup_prompt install_node "Install Node.js via $pkg_manager? [Y/n]: " "Y"
|
|
1513
|
-
if [[ "$install_node" =~ ^[Yy]?$ ]]; then
|
|
1514
|
-
if sudo "$pkg_manager" install -y nodejs npm; then
|
|
1515
|
-
print_success "Node.js installed: $(node --version)"
|
|
1516
|
-
else
|
|
1517
|
-
print_warning "Node.js installation failed"
|
|
1518
|
-
fi
|
|
1519
|
-
fi
|
|
1520
|
-
;;
|
|
1521
|
-
pacman)
|
|
1522
|
-
setup_prompt install_node "Install Node.js via pacman? [Y/n]: " "Y"
|
|
1523
|
-
if [[ "$install_node" =~ ^[Yy]?$ ]]; then
|
|
1524
|
-
if sudo pacman -S --noconfirm nodejs npm; then
|
|
1525
|
-
print_success "Node.js installed: $(node --version)"
|
|
1526
|
-
else
|
|
1527
|
-
print_warning "Node.js installation failed"
|
|
1528
|
-
fi
|
|
1529
|
-
fi
|
|
1530
|
-
;;
|
|
1531
|
-
apk)
|
|
1532
|
-
setup_prompt install_node "Install Node.js via apk? [Y/n]: " "Y"
|
|
1533
|
-
if [[ "$install_node" =~ ^[Yy]?$ ]]; then
|
|
1534
|
-
if sudo apk add nodejs npm; then
|
|
1535
|
-
print_success "Node.js installed: $(node --version)"
|
|
1536
|
-
else
|
|
1537
|
-
print_warning "Node.js installation failed"
|
|
1538
|
-
fi
|
|
1539
|
-
fi
|
|
1540
|
-
;;
|
|
1541
|
-
*)
|
|
1542
|
-
print_warning "No supported package manager found for Node.js installation"
|
|
1543
|
-
echo " Install manually: https://nodejs.org/"
|
|
1544
|
-
;;
|
|
1545
|
-
esac
|
|
1546
|
-
|
|
1547
|
-
return 0
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
# Bound OpenCode setup probes/installers so non-interactive setup cannot hang
|
|
1551
|
-
# indefinitely when an opencode shim or package manager blocks.
|
|
1552
|
-
_setup_opencode_timeout_cmd() {
|
|
1553
|
-
local timeout_seconds="$1"
|
|
1554
|
-
shift
|
|
1555
|
-
|
|
1556
|
-
[[ "$timeout_seconds" =~ ^[0-9]+$ ]] || timeout_seconds=5
|
|
1557
|
-
[[ "$timeout_seconds" -gt 0 ]] || timeout_seconds=5
|
|
1558
|
-
|
|
1559
|
-
local command_name="${1:-}"
|
|
1560
|
-
if [[ -n "$command_name" ]] && ! declare -F "$command_name" >/dev/null 2>&1; then
|
|
1561
|
-
if declare -F timeout_sec >/dev/null 2>&1; then
|
|
1562
|
-
timeout_sec "$timeout_seconds" "$@"
|
|
1563
|
-
return $?
|
|
1564
|
-
fi
|
|
1565
|
-
|
|
1566
|
-
if command -v gtimeout >/dev/null 2>&1; then
|
|
1567
|
-
gtimeout "$timeout_seconds" "$@"
|
|
1568
|
-
return $?
|
|
1569
|
-
fi
|
|
1570
|
-
|
|
1571
|
-
if command -v timeout >/dev/null 2>&1; then
|
|
1572
|
-
timeout "$timeout_seconds" "$@"
|
|
1573
|
-
return $?
|
|
1574
|
-
fi
|
|
1575
|
-
fi
|
|
1576
|
-
|
|
1577
|
-
local output_file=""
|
|
1578
|
-
output_file=$(mktemp "${TMPDIR:-/tmp}/aidevops-opencode-timeout.XXXXXX" 2>/dev/null || printf '')
|
|
1579
|
-
if [[ -z "$output_file" ]]; then
|
|
1580
|
-
"$@"
|
|
1581
|
-
return $?
|
|
1582
|
-
fi
|
|
1583
|
-
|
|
1584
|
-
local pid=""
|
|
1585
|
-
"$@" >"$output_file" 2>&1 &
|
|
1586
|
-
pid=$!
|
|
1587
|
-
|
|
1588
|
-
local elapsed=0
|
|
1589
|
-
while kill -0 "$pid" 2>/dev/null; do
|
|
1590
|
-
if [[ "$elapsed" -ge "$timeout_seconds" ]]; then
|
|
1591
|
-
kill "$pid" 2>/dev/null || true
|
|
1592
|
-
wait "$pid" 2>/dev/null || true
|
|
1593
|
-
rm -f "$output_file" 2>/dev/null || true
|
|
1594
|
-
return 124
|
|
1595
|
-
fi
|
|
1596
|
-
sleep 1
|
|
1597
|
-
elapsed=$((elapsed + 1))
|
|
1598
|
-
done
|
|
1599
|
-
|
|
1600
|
-
local rc=0
|
|
1601
|
-
wait "$pid" || rc=$?
|
|
1602
|
-
while IFS= read -r line; do
|
|
1603
|
-
printf '%s\n' "$line"
|
|
1604
|
-
done <"$output_file"
|
|
1605
|
-
rm -f "$output_file" 2>/dev/null || true
|
|
1606
|
-
return "$rc"
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
_setup_opencode_version_output() {
|
|
1610
|
-
local bin="$1"
|
|
1611
|
-
local version_timeout="${AIDEVOPS_OPENCODE_VERSION_TIMEOUT:-5}"
|
|
1612
|
-
|
|
1613
|
-
_setup_opencode_timeout_cmd "$version_timeout" "$bin" --version
|
|
1614
|
-
return $?
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
_setup_opencode_first_line() {
|
|
1618
|
-
local input="$1"
|
|
1619
|
-
local first_line=""
|
|
1620
|
-
|
|
1621
|
-
IFS= read -r first_line <<<"$input" || true
|
|
1622
|
-
printf '%s\n' "$first_line"
|
|
1623
|
-
return 0
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
# t2891: Validate that an opencode binary is real anomalyco/opencode.
|
|
1627
|
-
# Mirrors the t2887 runtime canary validator (headless-runtime-lib.sh) and
|
|
1628
|
-
# the t2888 setup module validator (.agents/scripts/setup/_services.sh).
|
|
1629
|
-
# Inlined to keep tool-install.sh self-contained — sourced from setup.sh
|
|
1630
|
-
# during early bootstrap before headless-runtime-lib.sh is on the path.
|
|
1631
|
-
# Returns: 0=valid, 1=wrong package (e.g. claude CLI), 2=missing/unrunnable.
|
|
1632
|
-
_setup_validate_opencode_binary() {
|
|
1633
|
-
local bin="${1:-}"
|
|
1634
|
-
[[ -n "$bin" ]] || return 2
|
|
1635
|
-
command -v "$bin" >/dev/null 2>&1 || return 2
|
|
1636
|
-
|
|
1637
|
-
local v
|
|
1638
|
-
v=$(_setup_opencode_version_output "$bin" 2>/dev/null || printf '')
|
|
1639
|
-
[[ -n "$v" ]] || return 2
|
|
1640
|
-
|
|
1641
|
-
# Anthropic claude CLI signature — highest-confidence rejection.
|
|
1642
|
-
[[ "$v" == *"(Claude Code)"* ]] && return 1
|
|
1643
|
-
|
|
1644
|
-
# opencode is at 1.x; any 2.x+ is wrong (claude CLI is 2.1.x).
|
|
1645
|
-
[[ "$v" =~ ^[2-9][0-9]*\. ]] && return 1
|
|
1646
|
-
|
|
1647
|
-
# Sanity: must look like a semver (X.Y.Z).
|
|
1648
|
-
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]] || return 1
|
|
1649
|
-
|
|
1650
|
-
return 0
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
# t2891: detect-and-heal when 'opencode' bin name is owned by a wrong
|
|
1654
|
-
# package (canonical case: @anthropic-ai/claude-code shadowing
|
|
1655
|
-
# anomalyco/opencode after a npm install collision). Auto-installs
|
|
1656
|
-
# without prompt because:
|
|
1657
|
-
# 1) it's healing a broken state, not first-time setup
|
|
1658
|
-
# 2) non-interactive runners (the canonical victim) would auto-Y anyway
|
|
1659
|
-
# Idempotent. Fail-open if no installer available.
|
|
1660
|
-
_setup_opencode_force_heal() {
|
|
1661
|
-
local install_pkg="$1" wrong_bin="$2" wrong_v="$3"
|
|
1662
|
-
print_warning "OpenCode binary at '$wrong_bin' is the wrong package ('$wrong_v')"
|
|
1663
|
-
print_info "Forcing reinstall of $install_pkg to heal bin collision (t2891)..."
|
|
1664
|
-
|
|
1665
|
-
local installer=""
|
|
1666
|
-
if command -v bun >/dev/null 2>&1; then
|
|
1667
|
-
installer="bun"
|
|
1668
|
-
elif command -v npm >/dev/null 2>&1; then
|
|
1669
|
-
installer="npm"
|
|
1670
|
-
else
|
|
1671
|
-
print_warning "Neither bun nor npm found — cannot heal OpenCode binary"
|
|
1672
|
-
print_info "Install Node.js or Bun first, then re-run 'aidevops update'"
|
|
1673
|
-
return 0
|
|
1674
|
-
fi
|
|
1675
|
-
|
|
1676
|
-
local install_timeout="${AIDEVOPS_OPENCODE_INSTALL_TIMEOUT:-180}"
|
|
1677
|
-
if run_with_spinner "Reinstalling OpenCode (heal)" _setup_opencode_timeout_cmd "$install_timeout" npm_global_install "$install_pkg"; then
|
|
1678
|
-
print_success "OpenCode reinstalled via $installer"
|
|
1679
|
-
else
|
|
1680
|
-
print_warning "Heal install failed via $installer"
|
|
1681
|
-
print_info "Try manually: $installer install -g $install_pkg"
|
|
1682
|
-
fi
|
|
1683
|
-
|
|
1684
|
-
# Re-validate post-heal.
|
|
1685
|
-
local new_bin
|
|
1686
|
-
new_bin=$(command -v opencode 2>/dev/null || echo "")
|
|
1687
|
-
if [[ -n "$new_bin" ]] && _setup_validate_opencode_binary "$new_bin"; then
|
|
1688
|
-
local new_v
|
|
1689
|
-
new_v=$(_setup_opencode_first_line "$(_setup_opencode_version_output "$new_bin" 2>/dev/null || printf 'unknown')")
|
|
1690
|
-
print_success "OpenCode CLI: $new_bin ($new_v)"
|
|
1691
|
-
mkdir -p "${HOME}/.aidevops" 2>/dev/null || true
|
|
1692
|
-
printf '%s\n' "$new_bin" >"${HOME}/.aidevops/.opencode-bin-resolved" 2>/dev/null || true
|
|
1693
|
-
else
|
|
1694
|
-
local v_after
|
|
1695
|
-
v_after=$(_setup_opencode_first_line "$(_setup_opencode_version_output "$new_bin" 2>/dev/null || printf '<missing>')")
|
|
1696
|
-
print_warning "Post-heal validation still failing: '$new_bin' returns '$v_after'"
|
|
1697
|
-
print_info "Check PATH: 'which -a opencode' — npm/bun global bin dir must come first"
|
|
1698
|
-
fi
|
|
1699
|
-
return 0
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
setup_opencode_cli() {
|
|
1703
|
-
print_info "Setting up OpenCode CLI..."
|
|
1704
|
-
|
|
1705
|
-
# Respect OPENCODE_PINNED_VERSION from shared-constants.sh if sourced,
|
|
1706
|
-
# otherwise fall back to latest.
|
|
1707
|
-
local pin_ver="${OPENCODE_PINNED_VERSION:-latest}"
|
|
1708
|
-
local install_pkg="opencode-ai@${pin_ver}"
|
|
1709
|
-
|
|
1710
|
-
# t2891: validate the resolved binary is anomalyco/opencode, not a
|
|
1711
|
-
# wrong package (claude CLI etc) that took the 'opencode' bin name.
|
|
1712
|
-
# Without this, alex-solovyev's runner — where command -v opencode
|
|
1713
|
-
# resolves to @anthropic-ai/claude-code — silently passes through
|
|
1714
|
-
# this function, leaving t2887's runtime canary to throttle the spam
|
|
1715
|
-
# without ever healing the binary.
|
|
1716
|
-
local current_bin
|
|
1717
|
-
current_bin=$(command -v opencode 2>/dev/null || echo "")
|
|
1718
|
-
local validate_rc=0
|
|
1719
|
-
if [[ -n "$current_bin" ]]; then
|
|
1720
|
-
_setup_validate_opencode_binary "$current_bin" || validate_rc=$?
|
|
1721
|
-
else
|
|
1722
|
-
validate_rc=2
|
|
1723
|
-
fi
|
|
1724
|
-
|
|
1725
|
-
# Already valid → record + early return.
|
|
1726
|
-
if [[ $validate_rc -eq 0 ]]; then
|
|
1727
|
-
local oc_version
|
|
1728
|
-
oc_version=$(_setup_opencode_first_line "$(_setup_opencode_version_output "$current_bin" 2>/dev/null || printf 'unknown')")
|
|
1729
|
-
print_success "OpenCode already installed: $oc_version"
|
|
1730
|
-
mkdir -p "${HOME}/.aidevops" 2>/dev/null || true
|
|
1731
|
-
printf '%s\n' "$current_bin" >"${HOME}/.aidevops/.opencode-bin-resolved" 2>/dev/null || true
|
|
1732
|
-
return 0
|
|
1733
|
-
fi
|
|
1734
|
-
|
|
1735
|
-
# Wrong package → auto-heal (no prompt).
|
|
1736
|
-
if [[ $validate_rc -eq 1 ]]; then
|
|
1737
|
-
local wrong_v
|
|
1738
|
-
wrong_v=$(_setup_opencode_first_line "$(_setup_opencode_version_output "$current_bin" 2>/dev/null || printf '<unknown>')")
|
|
1739
|
-
_setup_opencode_force_heal "$install_pkg" "$current_bin" "$wrong_v"
|
|
1740
|
-
return 0
|
|
1741
|
-
fi
|
|
1742
|
-
|
|
1743
|
-
# Missing → first-time install path (preserves prompt for interactive UX).
|
|
1744
|
-
local installer=""
|
|
1745
|
-
if command -v bun >/dev/null 2>&1; then
|
|
1746
|
-
installer="bun"
|
|
1747
|
-
elif command -v npm >/dev/null 2>&1; then
|
|
1748
|
-
installer="npm"
|
|
1749
|
-
else
|
|
1750
|
-
print_warning "Neither bun nor npm found - cannot install OpenCode"
|
|
1751
|
-
print_info "Install Node.js first, then re-run setup"
|
|
1752
|
-
return 0
|
|
1753
|
-
fi
|
|
1754
|
-
|
|
1755
|
-
print_info "OpenCode is the AI coding tool that aidevops is built for"
|
|
1756
|
-
echo " It provides an AI-powered terminal interface for development tasks."
|
|
1757
|
-
echo ""
|
|
1758
|
-
|
|
1759
|
-
local install_oc
|
|
1760
|
-
setup_prompt install_oc "Install OpenCode via $installer? [Y/n]: " "Y"
|
|
1761
|
-
if [[ "$install_oc" =~ ^[Yy]?$ ]]; then
|
|
1762
|
-
local install_timeout="${AIDEVOPS_OPENCODE_INSTALL_TIMEOUT:-180}"
|
|
1763
|
-
if run_with_spinner "Installing OpenCode" _setup_opencode_timeout_cmd "$install_timeout" npm_global_install "$install_pkg"; then
|
|
1764
|
-
print_success "OpenCode installed"
|
|
1765
|
-
|
|
1766
|
-
# Persist resolved path on first-time success too (t2891).
|
|
1767
|
-
local new_bin
|
|
1768
|
-
new_bin=$(command -v opencode 2>/dev/null || echo "")
|
|
1769
|
-
if [[ -n "$new_bin" ]] && _setup_validate_opencode_binary "$new_bin"; then
|
|
1770
|
-
mkdir -p "${HOME}/.aidevops" 2>/dev/null || true
|
|
1771
|
-
printf '%s\n' "$new_bin" >"${HOME}/.aidevops/.opencode-bin-resolved" 2>/dev/null || true
|
|
1772
|
-
fi
|
|
1773
|
-
|
|
1774
|
-
# Offer authentication
|
|
1775
|
-
echo ""
|
|
1776
|
-
print_info "OpenCode needs authentication to use AI models."
|
|
1777
|
-
print_info "Run 'opencode auth login' to authenticate."
|
|
1778
|
-
echo ""
|
|
1779
|
-
else
|
|
1780
|
-
print_warning "OpenCode installation failed"
|
|
1781
|
-
print_info "Try manually: sudo npm install -g $install_pkg"
|
|
1782
|
-
fi
|
|
1783
|
-
else
|
|
1784
|
-
print_info "Skipped OpenCode installation"
|
|
1785
|
-
print_info "Install later: $installer install -g $install_pkg"
|
|
1786
|
-
fi
|
|
1787
|
-
|
|
1788
|
-
return 0
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
setup_codex_cli() {
|
|
1792
|
-
print_info "Setting up OpenAI Codex CLI..."
|
|
1793
|
-
|
|
1794
|
-
# Check if Codex is already installed
|
|
1795
|
-
if command -v codex >/dev/null 2>&1; then
|
|
1796
|
-
local codex_version
|
|
1797
|
-
codex_version=$(codex --version 2>/dev/null | head -1 || echo "unknown")
|
|
1798
|
-
print_success "Codex already installed: $codex_version"
|
|
1799
|
-
# Fix broken MCP_DOCKER if present
|
|
1800
|
-
_fix_codex_docker_mcp
|
|
1801
|
-
return 0
|
|
1802
|
-
fi
|
|
1803
|
-
|
|
1804
|
-
# Need either bun or npm to install
|
|
1805
|
-
local installer=""
|
|
1806
|
-
local install_pkg="@openai/codex@latest"
|
|
1807
|
-
|
|
1808
|
-
if command -v bun >/dev/null 2>&1; then
|
|
1809
|
-
installer="bun"
|
|
1810
|
-
elif command -v npm >/dev/null 2>&1; then
|
|
1811
|
-
installer="npm"
|
|
1812
|
-
else
|
|
1813
|
-
print_warning "Neither bun nor npm found - cannot install Codex"
|
|
1814
|
-
print_info "Install Node.js first, then re-run setup"
|
|
1815
|
-
return 0
|
|
1816
|
-
fi
|
|
1817
|
-
|
|
1818
|
-
print_info "Codex is OpenAI's AI coding CLI (terminal-based, agentic)"
|
|
1819
|
-
echo " It provides an AI-powered terminal interface using OpenAI models."
|
|
1820
|
-
echo ""
|
|
1821
|
-
|
|
1822
|
-
local install_codex
|
|
1823
|
-
setup_prompt install_codex "Install Codex via $installer? [Y/n]: " "Y"
|
|
1824
|
-
if [[ "$install_codex" =~ ^[Yy]?$ ]]; then
|
|
1825
|
-
if run_with_spinner "Installing Codex" npm_global_install "$install_pkg"; then
|
|
1826
|
-
print_success "Codex installed"
|
|
1827
|
-
echo ""
|
|
1828
|
-
print_info "Codex needs OpenAI authentication."
|
|
1829
|
-
print_info "Run 'codex' and follow the auth prompts."
|
|
1830
|
-
echo ""
|
|
1831
|
-
# Fix broken MCP_DOCKER if Codex created a default config
|
|
1832
|
-
_fix_codex_docker_mcp
|
|
1833
|
-
else
|
|
1834
|
-
print_warning "Codex installation failed"
|
|
1835
|
-
print_info "Try manually: npm install -g $install_pkg"
|
|
1836
|
-
fi
|
|
1837
|
-
else
|
|
1838
|
-
print_info "Skipped Codex installation"
|
|
1839
|
-
print_info "Install later: $installer install -g $install_pkg"
|
|
1840
|
-
fi
|
|
1841
|
-
|
|
1842
|
-
return 0
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
# P0 fix: Remove broken MCP_DOCKER from Codex config.toml
|
|
1846
|
-
# Docker Desktop 4.40+ with MCP Toolkit extension is required for `docker mcp`.
|
|
1847
|
-
# OrbStack, Colima, Rancher Desktop do not support it.
|
|
1848
|
-
_fix_codex_docker_mcp() {
|
|
1849
|
-
local config="$HOME/.codex/config.toml"
|
|
1850
|
-
[[ -f "$config" ]] || return 0
|
|
1851
|
-
|
|
1852
|
-
# Check if MCP_DOCKER section exists
|
|
1853
|
-
if ! grep -q '^\[mcp_servers\.MCP_DOCKER\]' "$config" 2>/dev/null; then
|
|
1854
|
-
return 0
|
|
1855
|
-
fi
|
|
1856
|
-
|
|
1857
|
-
# Check if `docker mcp` subcommand is actually available
|
|
1858
|
-
if docker mcp --help >/dev/null 2>&1; then
|
|
1859
|
-
return 0
|
|
1860
|
-
fi
|
|
1861
|
-
|
|
1862
|
-
# Comment out the MCP_DOCKER section (from header to next section or EOF)
|
|
1863
|
-
# Use sed to comment out lines from [mcp_servers.MCP_DOCKER] to the next
|
|
1864
|
-
# section header or end of file. Portable sed (no -i on macOS without ext).
|
|
1865
|
-
local tmp_config
|
|
1866
|
-
tmp_config=$(mktemp)
|
|
1867
|
-
local in_mcp_docker=false
|
|
1868
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
1869
|
-
if [[ "$line" == "[mcp_servers.MCP_DOCKER]" ]]; then
|
|
1870
|
-
in_mcp_docker=true
|
|
1871
|
-
printf '# %s # Disabled by aidevops: docker mcp not available\n' "$line" >>"$tmp_config"
|
|
1872
|
-
continue
|
|
1873
|
-
fi
|
|
1874
|
-
# If we hit another section header, stop commenting
|
|
1875
|
-
if [[ "$in_mcp_docker" == "true" ]] && [[ "$line" == "["* ]]; then
|
|
1876
|
-
in_mcp_docker=false
|
|
1877
|
-
fi
|
|
1878
|
-
if [[ "$in_mcp_docker" == "true" ]]; then
|
|
1879
|
-
printf '# %s\n' "$line" >>"$tmp_config"
|
|
1880
|
-
else
|
|
1881
|
-
printf '%s\n' "$line" >>"$tmp_config"
|
|
1882
|
-
fi
|
|
1883
|
-
done <"$config"
|
|
1884
|
-
mv "$tmp_config" "$config"
|
|
1885
|
-
print_info "Disabled MCP_DOCKER in Codex config (docker mcp not available on this system)"
|
|
1886
|
-
return 0
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
setup_droid_cli() {
|
|
1890
|
-
print_info "Setting up Factory.AI Droid CLI..."
|
|
1891
|
-
|
|
1892
|
-
# Check if Droid is already installed
|
|
1893
|
-
if command -v droid >/dev/null 2>&1; then
|
|
1894
|
-
local droid_version
|
|
1895
|
-
droid_version=$(droid --version 2>/dev/null | head -1 || echo "unknown")
|
|
1896
|
-
print_success "Droid already installed: $droid_version"
|
|
1897
|
-
return 0
|
|
1898
|
-
fi
|
|
1899
|
-
|
|
1900
|
-
# Droid uses its own installer — not available via npm/brew
|
|
1901
|
-
print_info "Droid (Factory.AI) is an AI coding agent CLI"
|
|
1902
|
-
echo " It provides autonomous coding capabilities with Factory.AI models."
|
|
1903
|
-
echo ""
|
|
1904
|
-
|
|
1905
|
-
local install_droid
|
|
1906
|
-
setup_prompt install_droid "Install Droid CLI? [Y/n]: " "Y"
|
|
1907
|
-
if [[ "$install_droid" =~ ^[Yy]?$ ]]; then
|
|
1908
|
-
print_info "Installing Droid CLI..."
|
|
1909
|
-
if command -v curl >/dev/null 2>&1; then
|
|
1910
|
-
if curl -fsSL https://app.factory.ai/install.sh | bash 2>/dev/null; then
|
|
1911
|
-
print_success "Droid installed"
|
|
1912
|
-
echo ""
|
|
1913
|
-
print_info "Run 'droid auth login' to authenticate with Factory.AI."
|
|
1914
|
-
echo ""
|
|
1915
|
-
else
|
|
1916
|
-
print_warning "Droid installation failed"
|
|
1917
|
-
print_info "Install manually from: https://docs.factory.ai/cli/installation"
|
|
1918
|
-
fi
|
|
1919
|
-
else
|
|
1920
|
-
print_warning "curl not found - cannot install Droid"
|
|
1921
|
-
print_info "Install manually from: https://docs.factory.ai/cli/installation"
|
|
1922
|
-
fi
|
|
1923
|
-
else
|
|
1924
|
-
print_info "Skipped Droid installation"
|
|
1925
|
-
print_info "Install later: curl -fsSL https://app.factory.ai/install.sh | bash"
|
|
1926
|
-
fi
|
|
1927
|
-
|
|
1928
|
-
return 0
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
setup_google_workspace_cli() {
|
|
1932
|
-
print_info "Setting up Google Workspace CLI (gws)..."
|
|
1933
|
-
|
|
1934
|
-
# Check if gws is already installed
|
|
1935
|
-
if command -v gws >/dev/null 2>&1; then
|
|
1936
|
-
local gws_version
|
|
1937
|
-
gws_version=$(gws --version 2>/dev/null | head -1 || echo "unknown")
|
|
1938
|
-
print_success "Google Workspace CLI already installed: $gws_version"
|
|
1939
|
-
return 0
|
|
1940
|
-
fi
|
|
1941
|
-
|
|
1942
|
-
# Need either bun or npm to install
|
|
1943
|
-
local installer=""
|
|
1944
|
-
local install_pkg="@googleworkspace/cli@latest"
|
|
1945
|
-
|
|
1946
|
-
if command -v bun >/dev/null 2>&1; then
|
|
1947
|
-
installer="bun"
|
|
1948
|
-
elif command -v npm >/dev/null 2>&1; then
|
|
1949
|
-
installer="npm"
|
|
1950
|
-
else
|
|
1951
|
-
print_warning "Neither bun nor npm found - cannot install gws"
|
|
1952
|
-
print_info "Install Node.js first, then re-run setup"
|
|
1953
|
-
return 0
|
|
1954
|
-
fi
|
|
1955
|
-
|
|
1956
|
-
print_info "Google Workspace CLI provides Gmail, Calendar, Drive, and all Workspace APIs"
|
|
1957
|
-
echo " Used by Email, Business, and Accounts agents for Google Workspace integration."
|
|
1958
|
-
echo ""
|
|
1959
|
-
|
|
1960
|
-
local install_gws
|
|
1961
|
-
setup_prompt install_gws "Install Google Workspace CLI via $installer? [Y/n]: " "Y"
|
|
1962
|
-
if [[ "$install_gws" =~ ^[Yy]?$ ]]; then
|
|
1963
|
-
if run_with_spinner "Installing Google Workspace CLI" npm_global_install "$install_pkg"; then
|
|
1964
|
-
print_success "Google Workspace CLI installed"
|
|
1965
|
-
|
|
1966
|
-
echo ""
|
|
1967
|
-
print_info "Authentication required before use."
|
|
1968
|
-
print_info "Run 'gws auth setup' to authenticate with your Google account."
|
|
1969
|
-
print_info "For headless use: set GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE"
|
|
1970
|
-
echo ""
|
|
1971
|
-
else
|
|
1972
|
-
print_warning "Google Workspace CLI installation failed"
|
|
1973
|
-
print_info "Try manually: sudo npm install -g $install_pkg"
|
|
1974
|
-
fi
|
|
1975
|
-
else
|
|
1976
|
-
print_info "Skipped Google Workspace CLI installation"
|
|
1977
|
-
print_info "Install later: $installer install -g $install_pkg"
|
|
1978
|
-
fi
|
|
1979
|
-
|
|
1980
|
-
return 0
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
setup_orbstack_vm() {
|
|
1984
|
-
# Only available on macOS
|
|
1985
|
-
if [[ "$(uname)" != "Darwin" ]]; then
|
|
1986
|
-
return 0
|
|
1987
|
-
fi
|
|
1988
|
-
|
|
1989
|
-
# Check if OrbStack is already installed
|
|
1990
|
-
if [[ -d "/Applications/OrbStack.app" ]] || command -v orb >/dev/null 2>&1; then
|
|
1991
|
-
print_success "OrbStack already installed"
|
|
1992
|
-
return 0
|
|
1993
|
-
fi
|
|
1994
|
-
|
|
1995
|
-
print_info "OrbStack provides fast, lightweight Linux VMs on macOS"
|
|
1996
|
-
echo " You can run aidevops in an isolated Linux environment."
|
|
1997
|
-
echo " This is optional - aidevops works natively on macOS too."
|
|
1998
|
-
echo ""
|
|
1999
|
-
|
|
2000
|
-
if ! command -v brew >/dev/null 2>&1; then
|
|
2001
|
-
print_info "OrbStack available at: https://orbstack.dev/"
|
|
2002
|
-
return 0
|
|
2003
|
-
fi
|
|
2004
|
-
|
|
2005
|
-
setup_prompt install_orb "Install OrbStack? [y/N]: " "n"
|
|
2006
|
-
# shellcheck disable=SC2154 # set indirectly by setup_prompt via read
|
|
2007
|
-
if [[ "$install_orb" =~ ^[Yy]$ ]]; then
|
|
2008
|
-
if run_with_spinner "Installing OrbStack" brew install --cask orbstack; then
|
|
2009
|
-
print_success "OrbStack installed"
|
|
2010
|
-
print_info "Create a VM: orb create ubuntu aidevops"
|
|
2011
|
-
print_info "Then install aidevops inside: orb run aidevops bash <(curl -fsSL https://aidevops.sh/install)"
|
|
2012
|
-
else
|
|
2013
|
-
print_warning "OrbStack installation failed"
|
|
2014
|
-
print_info "Download manually: https://orbstack.dev/"
|
|
2015
|
-
fi
|
|
2016
|
-
else
|
|
2017
|
-
print_info "Skipped OrbStack installation"
|
|
2018
|
-
fi
|
|
2019
|
-
|
|
2020
|
-
return 0
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
setup_ai_orchestration() {
|
|
2024
|
-
print_info "Setting up AI orchestration frameworks..."
|
|
2025
|
-
|
|
2026
|
-
# Check Python — uses check_python_version from _common.sh to avoid
|
|
2027
|
-
# duplicating find_python3 → parse → compare → offer_python_brew_install logic.
|
|
2028
|
-
if ! check_python_version "" "AI orchestration" >/dev/null; then
|
|
2029
|
-
return 0
|
|
2030
|
-
fi
|
|
2031
|
-
|
|
2032
|
-
# Create orchestration directory
|
|
2033
|
-
mkdir -p "$HOME/.aidevops/orchestration"
|
|
2034
|
-
|
|
2035
|
-
# Info about available frameworks
|
|
2036
|
-
print_info "AI Orchestration Frameworks available:"
|
|
2037
|
-
echo " - Langflow: Visual flow builder (localhost:7860)"
|
|
2038
|
-
echo " - CrewAI: Multi-agent teams (localhost:8501)"
|
|
2039
|
-
echo " - AutoGen: Microsoft agentic AI (localhost:8081)"
|
|
2040
|
-
echo ""
|
|
2041
|
-
print_info "Setup individual frameworks with:"
|
|
2042
|
-
echo " bash .agents/scripts/langflow-helper.sh setup"
|
|
2043
|
-
echo " bash .agents/scripts/crewai-helper.sh setup"
|
|
2044
|
-
echo " bash .agents/scripts/autogen-helper.sh setup"
|
|
2045
|
-
echo ""
|
|
2046
|
-
print_info "See .agents/tools/ai-orchestration/overview.md for comparison"
|
|
2047
|
-
|
|
2048
|
-
return 0
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
# Prompt to install Ollama when the knowledge plane is enabled.
|
|
2052
|
-
# Called from _setup_run_interactive when the user opts in.
|
|
2053
|
-
# Installs Ollama and pulls the recommended fast model (llama3.1:8b).
|
|
2054
|
-
# The reasoning model (llama3.1:70b) and embed model are suggested but not
|
|
2055
|
-
# pulled automatically due to their large size (39 GB and 274 MB respectively).
|
|
2056
|
-
setup_ollama_for_knowledge() {
|
|
2057
|
-
print_info "Ollama — local LLM for knowledge plane (pii/sensitive/privileged tiers)"
|
|
2058
|
-
print_info "Required for: tier:pii, tier:sensitive, tier:privileged routing."
|
|
2059
|
-
|
|
2060
|
-
# Check if Ollama is already installed
|
|
2061
|
-
if command -v ollama >/dev/null 2>&1; then
|
|
2062
|
-
local version
|
|
2063
|
-
version=$(ollama --version 2>/dev/null | grep -o '[0-9][0-9.]*' | head -1) || version="unknown"
|
|
2064
|
-
print_success "Ollama already installed (version: ${version})"
|
|
2065
|
-
else
|
|
2066
|
-
print_info "Ollama not found."
|
|
2067
|
-
if [[ "$(uname -s)" == "Darwin" ]] && command -v brew >/dev/null 2>&1; then
|
|
2068
|
-
print_info "Installing Ollama via Homebrew..."
|
|
2069
|
-
if brew install ollama 2>/dev/null; then
|
|
2070
|
-
print_success "Ollama installed via Homebrew"
|
|
2071
|
-
else
|
|
2072
|
-
print_warning "Homebrew install failed. Download from https://ollama.com"
|
|
2073
|
-
return 0
|
|
2074
|
-
fi
|
|
2075
|
-
else
|
|
2076
|
-
print_info "Install Ollama manually from https://ollama.com"
|
|
2077
|
-
print_info "Then re-run this setup to pull the recommended models."
|
|
2078
|
-
return 0
|
|
2079
|
-
fi
|
|
2080
|
-
fi
|
|
2081
|
-
|
|
2082
|
-
# Deploy the bundle config template
|
|
2083
|
-
local bundle_dir="$HOME/.aidevops/configs"
|
|
2084
|
-
local bundle_dest="${bundle_dir}/ollama-bundle.json"
|
|
2085
|
-
local bundle_src
|
|
2086
|
-
bundle_src="${BASH_SOURCE[0]%/*}/../.agents/templates/ollama-bundle.json"
|
|
2087
|
-
if [[ ! -f "$bundle_src" ]]; then
|
|
2088
|
-
bundle_src="$HOME/.aidevops/agents/templates/ollama-bundle.json"
|
|
2089
|
-
fi
|
|
2090
|
-
if [[ -f "$bundle_src" ]] && [[ ! -f "$bundle_dest" ]]; then
|
|
2091
|
-
mkdir -p "$bundle_dir"
|
|
2092
|
-
cp "$bundle_src" "$bundle_dest"
|
|
2093
|
-
print_success "Ollama bundle config deployed: ${bundle_dest}"
|
|
2094
|
-
fi
|
|
2095
|
-
|
|
2096
|
-
# Start Ollama service if not running
|
|
2097
|
-
if ! curl -sf "http://localhost:11434/api/tags" >/dev/null 2>&1; then
|
|
2098
|
-
print_info "Starting Ollama service..."
|
|
2099
|
-
ollama serve >/dev/null 2>&1 &
|
|
2100
|
-
local i=0
|
|
2101
|
-
while [[ $i -lt 10 ]]; do
|
|
2102
|
-
if curl -sf "http://localhost:11434/api/tags" >/dev/null 2>&1; then
|
|
2103
|
-
break
|
|
2104
|
-
fi
|
|
2105
|
-
sleep 1
|
|
2106
|
-
i=$((i + 1))
|
|
2107
|
-
done
|
|
2108
|
-
fi
|
|
2109
|
-
|
|
2110
|
-
# Pull the fast model (minimum required for pii/sensitive tiers)
|
|
2111
|
-
print_info "Pulling minimum required model: llama3.1:8b (~4.9 GB)"
|
|
2112
|
-
print_info "This is required for tier:pii and tier:sensitive routing."
|
|
2113
|
-
if ollama pull llama3.1:8b 2>/dev/null; then
|
|
2114
|
-
print_success "llama3.1:8b pulled successfully"
|
|
2115
|
-
else
|
|
2116
|
-
print_warning "Failed to pull llama3.1:8b. Run manually: ollama pull llama3.1:8b"
|
|
2117
|
-
fi
|
|
2118
|
-
|
|
2119
|
-
# Pull the embed model (small — pull automatically)
|
|
2120
|
-
print_info "Pulling embed model: nomic-embed-text (~274 MB)"
|
|
2121
|
-
if ollama pull nomic-embed-text 2>/dev/null; then
|
|
2122
|
-
print_success "nomic-embed-text pulled successfully"
|
|
2123
|
-
else
|
|
2124
|
-
print_warning "Failed to pull nomic-embed-text. Run manually: ollama pull nomic-embed-text"
|
|
2125
|
-
fi
|
|
2126
|
-
|
|
2127
|
-
print_info ""
|
|
2128
|
-
print_info "Optional: pull the reasoning model for tier:privileged (~39 GB, requires 48+ GB RAM):"
|
|
2129
|
-
print_info " ollama pull llama3.1:70b"
|
|
2130
|
-
print_info ""
|
|
2131
|
-
print_info "Verify: ollama-helper.sh health"
|
|
2132
|
-
|
|
2133
|
-
return 0
|
|
2134
|
-
}
|