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
package/setup-modules/plugins.sh
DELETED
|
@@ -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
|
-
}
|