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.
@@ -1,728 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: MIT
3
- # SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
4
- # Plugin functions: deploy_plugins, sanitize_plugin_namespace, generate_agent_skills, create_skill_symlinks, check_skill_updates, scan_imported_skills, multi-tenant
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
- # Check if Python >= 3.10 is available (required by cisco-ai-skill-scanner).
15
- # Returns 0 if a compatible Python is found (or installed via uv), 1 otherwise.
16
- # On failure, prints a clear diagnostic with the version found and fix instructions.
17
- check_python_for_skill_scanner() {
18
- local required_major=3
19
- local required_minor=10
20
-
21
- # Helper: test whether a python binary meets the minimum version
22
- _python_version_ok() {
23
- local py_bin="$1"
24
- local ver_output
25
- ver_output=$("$py_bin" --version 2>/dev/null) || return 1
26
- # "Python 3.11.5" -> extract major.minor
27
- if [[ "$ver_output" != Python\ * ]]; then
28
- return 1
29
- fi
30
- local version major remainder minor
31
- version="${ver_output#Python }"
32
- major="${version%%.*}"
33
- remainder="${version#*.}"
34
- minor="${remainder%%.*}"
35
- if [[ ! "$major" =~ ^[0-9]+$ || ! "$minor" =~ ^[0-9]+$ ]]; then
36
- return 1
37
- fi
38
- if [[ "$major" -gt "$required_major" ]] ||
39
- { [[ "$major" -eq "$required_major" ]] && [[ "$minor" -ge "$required_minor" ]]; }; then
40
- return 0
41
- fi
42
- return 1
43
- }
44
-
45
- # 1. Check default python3
46
- if command -v python3 &>/dev/null && _python_version_ok python3; then
47
- return 0
48
- fi
49
-
50
- # 2. Check common versioned binaries (Homebrew, system)
51
- local py_bin
52
- for py_bin in python3.13 python3.12 python3.11 python3.10; do
53
- if command -v "$py_bin" &>/dev/null && _python_version_ok "$py_bin"; then
54
- return 0
55
- fi
56
- done
57
-
58
- # 3. If uv is available, install Python 3.11 and retry
59
- if command -v uv &>/dev/null; then
60
- print_info "No Python >= 3.10 found. Installing Python 3.11 via uv..."
61
- if uv python install 3.11; then
62
- # uv installs to its managed path; check if python3.11 is now available
63
- if command -v python3.11 &>/dev/null && _python_version_ok python3.11; then
64
- print_success "Python 3.11 installed via uv"
65
- return 0
66
- fi
67
- # uv may have installed it but not on PATH — check uv's python path
68
- local uv_py
69
- uv_py=$(uv python find 3.11 2>/dev/null) || true
70
- if [[ -n "$uv_py" ]] && _python_version_ok "$uv_py"; then
71
- print_success "Python 3.11 installed via uv (at $uv_py)"
72
- return 0
73
- fi
74
- print_warning "uv reported Python 3.11 installed, but verification failed"
75
- if [[ -n "$uv_py" ]]; then
76
- print_warning "Found interpreter at $uv_py, but version verification still failed"
77
- else
78
- print_warning "python3.11 is not on PATH and 'uv python find 3.11' did not return a usable path"
79
- fi
80
- print_info "Run 'uv python list' to confirm the install and update PATH if needed"
81
- else
82
- print_warning "uv python install 3.11 failed — see errors above"
83
- fi
84
- fi
85
-
86
- # 4. No compatible Python found — emit clear error
87
- local found_version="not installed"
88
- if command -v python3 &>/dev/null; then
89
- found_version=$(python3 --version 2>/dev/null || echo "unknown")
90
- fi
91
-
92
- print_warning "cisco-ai-skill-scanner requires Python >= 3.10, but found: $found_version"
93
- print_info "Fix options:"
94
- print_info " 1. brew install python@3.11 (macOS)"
95
- print_info " 2. uv python install 3.11 (cross-platform, recommended)"
96
- print_info " 3. sudo apt install python3.11 (Debian/Ubuntu)"
97
- print_info "After installing, re-run: aidevops update"
98
- return 1
99
- }
100
-
101
- sanitize_plugin_namespace() {
102
- local ns="$1"
103
- # Strip any path components, keep only the final directory name
104
- # This prevents ../../../etc/passwd and /absolute/paths
105
- ns=$(basename "$ns")
106
- # Additional safety: reject if it starts with . or contains suspicious chars
107
- if [[ "$ns" =~ ^\.|\.\.|[[:space:]]|[\\/] ]]; then
108
- return 1
109
- fi
110
- # Reject empty result
111
- if [[ -z "$ns" ]]; then
112
- return 1
113
- fi
114
- echo "$ns"
115
- return 0
116
- }
117
-
118
- deploy_plugins() {
119
- local target_dir="$1"
120
- local plugins_file="$2"
121
-
122
- # Skip if no plugins.json or no jq (GH#5240: clear skip messages)
123
- if [[ ! -f "$plugins_file" ]]; then
124
- return 0
125
- fi
126
- if ! command -v jq &>/dev/null; then
127
- print_skip "Plugin deployment" "jq not installed" "Install jq: brew install jq (macOS) or apt install jq"
128
- setup_track_deferred "Plugin deployment" "Install jq"
129
- return 0
130
- fi
131
-
132
- local plugin_count
133
- plugin_count=$(jq '.plugins | length' "$plugins_file" 2>/dev/null || echo "0")
134
- if [[ "$plugin_count" -eq 0 ]]; then
135
- return 0
136
- fi
137
-
138
- local enabled_count
139
- enabled_count=$(jq '[.plugins[] | select(.enabled != false)] | length' "$plugins_file" 2>/dev/null || echo "0")
140
- if [[ "$enabled_count" -eq 0 ]]; then
141
- print_info "No enabled plugins to deploy ($plugin_count configured, all disabled)"
142
- return 0
143
- fi
144
-
145
- # Remove directories for disabled plugins (cleanup)
146
- local disabled_ns
147
- local safe_ns
148
- while IFS= read -r disabled_ns; do
149
- [[ -z "$disabled_ns" ]] && continue
150
- # Sanitize namespace to prevent path traversal
151
- if ! safe_ns=$(sanitize_plugin_namespace "$disabled_ns"); then
152
- print_warning " Skipping invalid plugin namespace: $disabled_ns"
153
- continue
154
- fi
155
- if [[ -d "$target_dir/$safe_ns" ]]; then
156
- rm -rf "${target_dir:?}/${safe_ns:?}"
157
- print_info " Removed disabled plugin directory: $safe_ns"
158
- fi
159
- done < <(jq -r '.plugins[] | select(.enabled == false) | .namespace // empty' "$plugins_file" 2>/dev/null)
160
-
161
- print_info "Deploying $enabled_count plugin(s)..."
162
-
163
- local deployed=0
164
- local failed=0
165
- local skipped=0
166
-
167
- # Process each enabled plugin
168
- local safe_pns
169
- while IFS=$'\t' read -r pname prepo pns pbranch; do
170
- [[ -z "$pname" ]] && continue
171
- pbranch="${pbranch:-main}"
172
-
173
- # Sanitize namespace to prevent path traversal
174
- if ! safe_pns=$(sanitize_plugin_namespace "$pns"); then
175
- print_warning " Skipping plugin '$pname' with invalid namespace: $pns"
176
- failed=$((failed + 1))
177
- continue
178
- fi
179
-
180
- local clone_dir="$target_dir/$safe_pns"
181
-
182
- if [[ -d "$clone_dir" ]]; then
183
- # Plugin directory exists — skip re-clone during setup
184
- # Users can force update via: aidevops plugin update [name]
185
- skipped=$((skipped + 1))
186
- continue
187
- fi
188
-
189
- # Clone plugin repo
190
- print_info " Installing plugin '$pname' ($prepo)..."
191
- if git clone --branch "$pbranch" --depth 1 "$prepo" "$clone_dir" 2>/dev/null; then
192
- # Remove .git directory (tracked via plugins.json, not nested git)
193
- rm -rf "$clone_dir/.git"
194
- # Set permissions on any scripts
195
- if [[ -d "$clone_dir/scripts" ]]; then
196
- chmod +x "$clone_dir/scripts/"*.sh 2>/dev/null || true
197
- fi
198
- deployed=$((deployed + 1))
199
- else
200
- print_warning " Failed to install plugin '$pname' (network or auth issue)"
201
- failed=$((failed + 1))
202
- fi
203
- done < <(jq -r '.plugins[] | select(.enabled != false) | [.name, .repo, .namespace, (.branch // "main")] | @tsv' "$plugins_file" 2>/dev/null)
204
-
205
- # Summary
206
- if [[ "$deployed" -gt 0 ]]; then
207
- print_success "Deployed $deployed plugin(s)"
208
- fi
209
- if [[ "$skipped" -gt 0 ]]; then
210
- print_info "$skipped plugin(s) already deployed (use 'aidevops plugin update' to refresh)"
211
- fi
212
- if [[ "$failed" -gt 0 ]]; then
213
- print_warning "$failed plugin(s) failed to deploy (non-blocking)"
214
- fi
215
-
216
- return 0
217
- }
218
-
219
- generate_agent_skills() {
220
- print_info "Generating Agent Skills SKILL.md files..."
221
-
222
- local skills_script="$HOME/.aidevops/agents/scripts/generate-skills.sh"
223
- local timeout_seconds="${AIDEVOPS_SKILLS_GENERATE_TIMEOUT:-90}"
224
- local success_msg="Agent Skills SKILL.md files generated"
225
- local generate_rc=0
226
-
227
- if [[ ! -f "$skills_script" ]]; then
228
- print_warning "Agent Skills generator not found at $skills_script"
229
- return 1
230
- fi
231
-
232
- if command -v timeout >/dev/null 2>&1; then
233
- timeout "$timeout_seconds" bash "$skills_script" 2>/dev/null
234
- generate_rc=$?
235
- else
236
- # Portable fallback for macOS and other systems without GNU coreutils.
237
- # Run the generator in the background; poll every 5s; kill on overrun.
238
- bash "$skills_script" 2>/dev/null &
239
- local _gen_pid=$!
240
- local _elapsed=0
241
- while kill -0 "$_gen_pid" 2>/dev/null && [[ "$_elapsed" -lt "$timeout_seconds" ]]; do
242
- sleep 5
243
- _elapsed=$((_elapsed + 5))
244
- done
245
- if kill -0 "$_gen_pid" 2>/dev/null; then
246
- kill "$_gen_pid" 2>/dev/null
247
- wait "$_gen_pid" 2>/dev/null || true
248
- generate_rc=124
249
- else
250
- wait "$_gen_pid" 2>/dev/null || true
251
- generate_rc=$?
252
- fi
253
- fi
254
-
255
- if [[ "$generate_rc" -eq 0 ]]; then
256
- print_success "$success_msg"
257
- return 0
258
- elif [[ "$generate_rc" -eq 124 ]]; then
259
- print_warning "Agent Skills generation exceeded ${timeout_seconds}s — continuing without skill symlink refresh"
260
- return 1
261
- else
262
- print_warning "Agent Skills generation encountered issues (non-critical)"
263
- return 1
264
- fi
265
- }
266
-
267
- create_skill_symlinks() {
268
- print_info "Creating symlinks for imported skills..."
269
-
270
- local skill_sources="$HOME/.aidevops/agents/configs/skill-sources.json"
271
- local agents_dir="$HOME/.aidevops/agents"
272
-
273
- # Skip if no skill-sources.json or jq not available
274
- if [[ ! -f "$skill_sources" ]]; then
275
- print_info "No imported skills found (skill-sources.json not present)"
276
- return 0
277
- fi
278
-
279
- if ! command -v jq &>/dev/null; then
280
- print_warning "jq not found - cannot create skill symlinks"
281
- return 0
282
- fi
283
-
284
- # Check if there are any skills
285
- local skill_count
286
- skill_count=$(jq '.skills | length' "$skill_sources" 2>/dev/null || echo "0")
287
-
288
- if [[ "$skill_count" -eq 0 ]]; then
289
- print_info "No imported skills to symlink"
290
- return 0
291
- fi
292
-
293
- # AI assistant skill directories
294
- local skill_dirs=(
295
- "$HOME/.config/opencode/skills"
296
- "$HOME/.codex/skills"
297
- "$HOME/.claude/skills"
298
- "$HOME/.config/amp/tools"
299
- )
300
-
301
- # Create skill directories if they don't exist
302
- for dir in "${skill_dirs[@]}"; do
303
- mkdir -p "$dir" 2>/dev/null || true
304
- done
305
-
306
- local created_count=0
307
-
308
- # Read each skill and create symlinks
309
- while IFS= read -r skill_json; do
310
- local name local_path
311
- name=$(echo "$skill_json" | jq -r '.name')
312
- local_path=$(echo "$skill_json" | jq -r '.local_path')
313
-
314
- # Skip if path doesn't exist
315
- local full_path="$agents_dir/${local_path#.agents/}"
316
- if [[ ! -f "$full_path" ]]; then
317
- print_warning "Skill file not found: $full_path"
318
- continue
319
- fi
320
-
321
- # Create symlinks in each AI assistant directory
322
- for skill_dir in "${skill_dirs[@]}"; do
323
- local target_file
324
-
325
- # Amp expects <name>.md directly, others expect <name>/SKILL.md
326
- if [[ "$skill_dir" == *"/amp/tools" ]]; then
327
- target_file="$skill_dir/${name}.md"
328
- else
329
- local target_dir="$skill_dir/$name"
330
- target_file="$target_dir/SKILL.md"
331
- # Create skill subdirectory
332
- mkdir -p "$target_dir" 2>/dev/null || continue
333
- fi
334
-
335
- # Create symlink (remove existing first)
336
- rm -f "$target_file" 2>/dev/null || true
337
- if ln -sf "$full_path" "$target_file" 2>/dev/null; then
338
- ((++created_count))
339
- fi
340
- done
341
- done < <(jq -c '.skills[]' "$skill_sources" 2>/dev/null)
342
-
343
- if [[ $created_count -gt 0 ]]; then
344
- print_success "Created $created_count skill symlinks across AI assistants"
345
- else
346
- print_info "No skill symlinks created"
347
- fi
348
-
349
- return 0
350
- }
351
-
352
- check_skill_updates() {
353
- print_info "Checking for skill updates..."
354
-
355
- local skill_sources="$HOME/.aidevops/agents/configs/skill-sources.json"
356
-
357
- # Skip if no skill-sources.json or required tools not available
358
- if [[ ! -f "$skill_sources" ]]; then
359
- print_info "No imported skills to check"
360
- return 0
361
- fi
362
-
363
- if ! command -v jq &>/dev/null; then
364
- print_warning "jq not found - cannot check skill updates"
365
- return 0
366
- fi
367
-
368
- if ! command -v curl &>/dev/null; then
369
- print_warning "curl not found - cannot check skill updates"
370
- return 0
371
- fi
372
-
373
- local skill_count
374
- skill_count=$(jq '.skills | length' "$skill_sources" 2>/dev/null || echo "0")
375
-
376
- if [[ "$skill_count" -eq 0 ]]; then
377
- print_info "No imported skills to check"
378
- return 0
379
- fi
380
-
381
- local updates_available=0
382
- local update_list=""
383
-
384
- # Check each skill for updates
385
- while IFS= read -r skill_json; do
386
- local name upstream_url upstream_commit
387
- name=$(echo "$skill_json" | jq -r '.name')
388
- upstream_url=$(echo "$skill_json" | jq -r '.upstream_url')
389
- upstream_commit=$(echo "$skill_json" | jq -r '.upstream_commit // empty')
390
-
391
- # Skip skills without upstream URL or commit (e.g., context7 imports)
392
- if [[ -z "$upstream_url" || "$upstream_url" == "null" ]]; then
393
- continue
394
- fi
395
- if [[ -z "$upstream_commit" ]]; then
396
- continue
397
- fi
398
-
399
- # Extract owner/repo from GitHub URL
400
- local owner_repo
401
- owner_repo=$(echo "$upstream_url" | sed -E 's|https://github.com/||; s|\.git$||; s|/tree/.*||')
402
-
403
- if [[ -z "$owner_repo" || ! "$owner_repo" =~ / ]]; then
404
- continue
405
- fi
406
-
407
- # Get latest commit from GitHub API (silent, with timeout)
408
- local api_response latest_commit
409
- api_response=$(curl -s --max-time 5 "https://api.github.com/repos/$owner_repo/commits?per_page=1" 2>/dev/null)
410
-
411
- # Check if response is an array (success) or object (error like rate limit)
412
- if echo "$api_response" | jq -e 'type == "array"' >/dev/null 2>&1; then
413
- latest_commit=$(echo "$api_response" | jq -r '.[0].sha // empty')
414
- else
415
- # API returned error object, skip this skill
416
- continue
417
- fi
418
-
419
- if [[ -n "$latest_commit" && "$latest_commit" != "$upstream_commit" ]]; then
420
- ((++updates_available))
421
- update_list="${update_list}\n - $name (${upstream_commit:0:7} → ${latest_commit:0:7})"
422
- fi
423
- done < <(jq -c '.skills[]' "$skill_sources" 2>/dev/null)
424
-
425
- if [[ $updates_available -gt 0 ]]; then
426
- print_warning "Skill updates available:$update_list"
427
- print_info "Run: ~/.aidevops/agents/scripts/add-skill-helper.sh check-updates"
428
- print_info "To update a skill: ~/.aidevops/agents/scripts/add-skill-helper.sh add <url> --force"
429
- else
430
- print_success "All imported skills are up to date"
431
- fi
432
-
433
- return 0
434
- }
435
-
436
- # Find the skill-scanner binary on PATH or in known install locations.
437
- # Prints the path to the binary if found, empty string otherwise.
438
- # PATH note: uv/pipx install to ~/.local/bin which may not be on PATH in all shells.
439
- _find_skill_scanner_bin() {
440
- local bin
441
- bin=$(command -v skill-scanner 2>/dev/null || echo "")
442
- # Fallback: check the known install path directly
443
- if [[ -z "$bin" && -x "$HOME/.local/bin/skill-scanner" ]]; then
444
- bin="$HOME/.local/bin/skill-scanner"
445
- fi
446
- echo "$bin"
447
- return 0
448
- }
449
-
450
- # Find the first Python >= 3.10 interpreter available on this system.
451
- # Prints the interpreter path if found, empty string otherwise.
452
- # Checks the same candidates as check_python_for_skill_scanner() so the
453
- # venv method uses the same interpreter that passed validation.
454
- _find_python_for_skill_scanner() {
455
- local py_bin
456
- for py_bin in python3 python3.13 python3.12 python3.11 python3.10; do
457
- if command -v "$py_bin" &>/dev/null; then
458
- local ver_output
459
- ver_output=$("$py_bin" --version 2>/dev/null) || continue
460
- if [[ "$ver_output" != Python\ * ]]; then continue; fi
461
- local version major remainder minor
462
- version="${ver_output#Python }"
463
- major="${version%%.*}"
464
- remainder="${version#*.}"
465
- minor="${remainder%%.*}"
466
- if [[ ! "$major" =~ ^[0-9]+$ || ! "$minor" =~ ^[0-9]+$ ]]; then continue; fi
467
- if [[ "$major" -gt 3 ]] || { [[ "$major" -eq 3 ]] && [[ "$minor" -ge 10 ]]; }; then
468
- echo "$py_bin"
469
- return 0
470
- fi
471
- fi
472
- done
473
- echo ""
474
- return 1
475
- }
476
-
477
- # Install cisco-ai-skill-scanner using a 4-method fallback chain.
478
- # Fallback order: uv -> pipx -> venv+symlink -> pip3 --user (legacy)
479
- # PEP 668 (Ubuntu 24.04+) blocks pip3 --user, so isolated methods are tried first.
480
- # Prints the path to the installed binary on success, empty string on failure.
481
- _install_skill_scanner() {
482
- # Verify Python >= 3.10 is available (or install it via uv)
483
- if ! check_python_for_skill_scanner; then
484
- print_warning "Skipping Cisco Skill Scanner install (Python >= 3.10 required)"
485
- return 1
486
- fi
487
-
488
- local installed=false
489
- local skill_scanner_bin=""
490
-
491
- # 1. uv tool install (preferred - fast, isolated, manages its own Python)
492
- # Uses --force to handle two known failure modes:
493
- # a) Dangling/corrupted environment: uv detects "Invalid environment" and
494
- # exits 2. Detect via warning in `uv tool list` and uninstall first.
495
- # b) Executable conflict: skill-scanner already exists (e.g. from pipx).
496
- # --force overwrites the existing executable.
497
- if [[ "$installed" == "false" ]] && command -v uv &>/dev/null && uv tool --help &>/dev/null; then
498
- print_info "Installing Cisco Skill Scanner via uv..."
499
- # Detect and remove dangling uv environment before attempting install
500
- if uv tool list 2>&1 | grep -q "Ignoring malformed tool.*cisco-ai-skill-scanner"; then
501
- print_info "Removing dangling uv environment for cisco-ai-skill-scanner..."
502
- uv tool uninstall cisco-ai-skill-scanner 2>/dev/null || true
503
- fi
504
- if run_with_spinner "Installing cisco-ai-skill-scanner" uv tool install --force cisco-ai-skill-scanner; then
505
- print_success "Cisco Skill Scanner installed via uv"
506
- installed=true
507
- skill_scanner_bin=$(command -v skill-scanner 2>/dev/null || echo "$HOME/.local/bin/skill-scanner")
508
- fi
509
- fi
510
-
511
- # 2. pipx install (designed for isolated app installs)
512
- if [[ "$installed" == "false" ]] && command -v pipx &>/dev/null; then
513
- print_info "Installing Cisco Skill Scanner via pipx..."
514
- if run_with_spinner "Installing cisco-ai-skill-scanner" pipx install cisco-ai-skill-scanner; then
515
- print_success "Cisco Skill Scanner installed via pipx"
516
- installed=true
517
- skill_scanner_bin=$(command -v skill-scanner 2>/dev/null || echo "$HOME/.local/bin/skill-scanner")
518
- fi
519
- fi
520
-
521
- # 3. venv + symlink (works on PEP 668 systems without uv/pipx)
522
- # Use the validated interpreter (same candidates as check_python_for_skill_scanner)
523
- # so venv creation succeeds even when python3 itself is < 3.10.
524
- if [[ "$installed" == "false" ]]; then
525
- local py_interp
526
- py_interp=$(_find_python_for_skill_scanner) || true
527
- if [[ -n "$py_interp" ]]; then
528
- local venv_dir="$HOME/.aidevops/.agent-workspace/work/cisco-scanner-env"
529
- local bin_dir="$HOME/.local/bin"
530
- print_info "Installing Cisco Skill Scanner in isolated venv..."
531
- if "$py_interp" -m venv "$venv_dir" 2>/dev/null &&
532
- "$venv_dir/bin/pip" install cisco-ai-skill-scanner 2>/dev/null; then
533
- mkdir -p "$bin_dir"
534
- ln -sf "$venv_dir/bin/skill-scanner" "$bin_dir/skill-scanner"
535
- print_success "Cisco Skill Scanner installed via venv ($venv_dir)"
536
- installed=true
537
- skill_scanner_bin="$bin_dir/skill-scanner"
538
- else
539
- rm -rf "$venv_dir" 2>/dev/null || true
540
- fi
541
- fi
542
- fi
543
-
544
- # 4. pip3 --user (legacy fallback, fails on PEP 668 systems)
545
- if [[ "$installed" == "false" ]] && command -v pip3 &>/dev/null; then
546
- print_info "Installing Cisco Skill Scanner via pip3 --user..."
547
- if run_with_spinner "Installing cisco-ai-skill-scanner" pip3 install --user cisco-ai-skill-scanner 2>/dev/null; then
548
- print_success "Cisco Skill Scanner installed via pip3"
549
- installed=true
550
- skill_scanner_bin=$(command -v skill-scanner 2>/dev/null || echo "$HOME/.local/bin/skill-scanner")
551
- fi
552
- fi
553
-
554
- if [[ "$installed" == "false" ]]; then
555
- print_warning "Failed to install Cisco Skill Scanner - skipping security scan"
556
- print_info "Install manually with: uv tool install cisco-ai-skill-scanner"
557
- print_info "Or: pipx install cisco-ai-skill-scanner"
558
- return 1
559
- fi
560
-
561
- echo "$skill_scanner_bin"
562
- return 0
563
- }
564
-
565
- scan_imported_skills() {
566
- # Check prerequisites before announcing setup (GH#5240)
567
- local security_helper="$HOME/.aidevops/agents/scripts/security-helper.sh"
568
-
569
- if [[ ! -f "$security_helper" ]]; then
570
- print_skip "Skill security scan" "security-helper.sh not found" "Deploy agents first (setup.sh), then re-run"
571
- setup_track_skipped "Skill security scan" "security-helper.sh not found"
572
- return 0
573
- fi
574
-
575
- # Prerequisites met — proceed with setup
576
- print_info "Running security scan on imported skills..."
577
-
578
- # Locate or install skill-scanner binary
579
- local skill_scanner_bin
580
- skill_scanner_bin=$(_find_skill_scanner_bin)
581
-
582
- if [[ -z "$skill_scanner_bin" ]]; then
583
- # _install_skill_scanner prints the binary path on success
584
- skill_scanner_bin=$(_install_skill_scanner) || return 0
585
- fi
586
-
587
- if [[ ! -x "$skill_scanner_bin" ]]; then
588
- print_warning "skill-scanner not executable at: $skill_scanner_bin"
589
- return 0
590
- fi
591
-
592
- # Prepend the scanner's directory to PATH so security-helper.sh can resolve
593
- # skill-scanner even when ~/.local/bin is not in the caller's PATH.
594
- local scanner_dir
595
- scanner_dir=$(dirname "$skill_scanner_bin")
596
- if PATH="$scanner_dir:$PATH" bash "$security_helper" skill-scan all 2>/dev/null; then
597
- print_success "All imported skills passed security scan"
598
- else
599
- print_warning "Some imported skills have security findings - review with: aidevops skill scan"
600
- fi
601
-
602
- return 0
603
- }
604
-
605
- setup_multi_tenant_credentials() {
606
- # Check prerequisites before announcing setup (GH#5240)
607
- local credential_helper="$HOME/.aidevops/agents/scripts/credential-helper.sh"
608
-
609
- if [[ ! -f "$credential_helper" ]]; then
610
- # Try local script if deployed version not available yet
611
- credential_helper=".agents/scripts/credential-helper.sh"
612
- fi
613
-
614
- if [[ ! -f "$credential_helper" ]]; then
615
- print_skip "Multi-tenant credentials" "credential-helper.sh not found" "Deploy agents first (setup.sh), then re-run"
616
- setup_track_skipped "Multi-tenant credentials" "credential-helper.sh not found"
617
- return 0
618
- fi
619
-
620
- # Prerequisites met — proceed with setup
621
- print_info "Multi-tenant credential storage..."
622
-
623
- # Check if already initialized
624
- if [[ -d "$HOME/.config/aidevops/tenants" ]]; then
625
- local tenant_count
626
- tenant_count=$(find "$HOME/.config/aidevops/tenants" -maxdepth 1 -type d | wc -l)
627
- # Subtract 1 for the tenants/ dir itself
628
- tenant_count=$((tenant_count - 1))
629
- print_success "Multi-tenant already initialized ($tenant_count tenant(s))"
630
- bash "$credential_helper" status
631
- return 0
632
- fi
633
-
634
- # Check if there are existing credentials to migrate
635
- if [[ -f "$HOME/.config/aidevops/credentials.sh" ]]; then
636
- local key_count
637
- key_count=$(grep -c "^export " "$HOME/.config/aidevops/credentials.sh" 2>/dev/null) || true
638
- print_info "Found $key_count existing API keys in credentials.sh"
639
- print_info "Multi-tenant enables managing separate credential sets for:"
640
- echo " - Multiple clients (agency/freelance work)"
641
- echo " - Multiple environments (production, staging)"
642
- echo " - Multiple accounts (personal, work)"
643
- echo ""
644
- print_info "Your existing keys will be migrated to a 'default' tenant."
645
- print_info "Everything continues to work as before - this is non-breaking."
646
- echo ""
647
-
648
- setup_prompt enable_mt "Enable multi-tenant credential storage? [Y/n]: " "Y"
649
- enable_mt=$(echo "$enable_mt" | tr '[:upper:]' '[:lower:]')
650
-
651
- if [[ "$enable_mt" =~ ^[Yy]?$ || "$enable_mt" == "yes" ]]; then
652
- bash "$credential_helper" init
653
- print_success "Multi-tenant credential storage enabled"
654
- echo ""
655
- print_info "Quick start:"
656
- echo " credential-helper.sh create client-name # Create a tenant"
657
- echo " credential-helper.sh switch client-name # Switch active tenant"
658
- echo " credential-helper.sh set KEY val --tenant X # Add key to tenant"
659
- echo " credential-helper.sh status # Show current state"
660
- else
661
- print_info "Skipped. Enable later: credential-helper.sh init"
662
- fi
663
- else
664
- print_info "No existing credentials found. Multi-tenant available when needed."
665
- print_info "Enable later: credential-helper.sh init"
666
- fi
667
-
668
- return 0
669
- }
670
-
671
- check_tool_updates() {
672
- # Check prerequisites before announcing setup (GH#5240)
673
- local tool_check_script="$HOME/.aidevops/agents/scripts/tool-version-check.sh"
674
-
675
- if [[ ! -f "$tool_check_script" ]]; then
676
- # Try local script if deployed version not available yet
677
- tool_check_script=".agents/scripts/tool-version-check.sh"
678
- fi
679
-
680
- if [[ ! -f "$tool_check_script" ]]; then
681
- print_skip "Tool updates" "version check script not found" "Deploy agents first (setup.sh), then re-run"
682
- setup_track_skipped "Tool updates" "version check script not found"
683
- return 0
684
- fi
685
-
686
- # Prerequisites met — proceed with setup
687
- print_info "Checking for tool updates..."
688
-
689
- # Run the check in quiet mode first to see if there are updates
690
- # Capture both output and exit code
691
- local outdated_output
692
- local check_exit_code
693
- outdated_output=$(bash "$tool_check_script" --quiet 2>&1) || check_exit_code=$?
694
- check_exit_code=${check_exit_code:-0}
695
-
696
- # If the script failed, warn and continue
697
- if [[ $check_exit_code -ne 0 ]]; then
698
- print_warning "Tool version check encountered an error (exit code: $check_exit_code)"
699
- print_info "Run 'aidevops update-tools' manually to check for updates"
700
- return 0
701
- fi
702
-
703
- if [[ -z "$outdated_output" ]]; then
704
- print_success "All tools are up to date!"
705
- return 0
706
- fi
707
-
708
- # Show what's outdated
709
- echo ""
710
- print_warning "Some tools have updates available:"
711
- echo ""
712
- bash "$tool_check_script" --quiet
713
- echo ""
714
-
715
- local do_update=""
716
- setup_prompt do_update "Update all outdated tools now? [Y/n]: " "Y"
717
-
718
- if [[ "$do_update" =~ ^[Yy]?$ || "$do_update" == "Y" ]]; then
719
- print_info "Updating tools..."
720
- bash "$tool_check_script" --update
721
- print_success "Tool updates complete!"
722
- else
723
- print_info "Skipped tool updates"
724
- print_info "Run 'aidevops update-tools' anytime to update tools"
725
- fi
726
-
727
- return 0
728
- }