aidevops 2.172.18 → 2.172.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +2 -1
- package/scripts/npm-postinstall.cjs +13 -2
- package/setup-modules/agent-deploy.sh +627 -0
- package/setup-modules/config.sh +183 -0
- package/setup-modules/core.sh +572 -0
- package/setup-modules/mcp-setup.sh +766 -0
- package/setup-modules/migrations.sh +961 -0
- package/setup-modules/plugins.sh +588 -0
- package/setup-modules/shell-env.sh +892 -0
- package/setup-modules/tool-install.sh +1373 -0
- package/setup.sh +1 -1
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Plugin functions: deploy_plugins, sanitize_plugin_namespace, generate_agent_skills, create_skill_symlinks, check_skill_updates, scan_imported_skills, multi-tenant
|
|
3
|
+
# Part of aidevops setup.sh modularization (t316.3)
|
|
4
|
+
|
|
5
|
+
# Shell safety baseline
|
|
6
|
+
set -Eeuo pipefail
|
|
7
|
+
IFS=$'\n\t'
|
|
8
|
+
# shellcheck disable=SC2154 # rc is assigned by $? in the trap string
|
|
9
|
+
trap 'rc=$?; echo "[ERROR] ${BASH_SOURCE[0]}:${LINENO} exit $rc" >&2' ERR
|
|
10
|
+
shopt -s inherit_errexit 2>/dev/null || true
|
|
11
|
+
|
|
12
|
+
# Check if Python >= 3.10 is available (required by cisco-ai-skill-scanner).
|
|
13
|
+
# Returns 0 if a compatible Python is found (or installed via uv), 1 otherwise.
|
|
14
|
+
# On failure, prints a clear diagnostic with the version found and fix instructions.
|
|
15
|
+
check_python_for_skill_scanner() {
|
|
16
|
+
local required_major=3
|
|
17
|
+
local required_minor=10
|
|
18
|
+
|
|
19
|
+
# Helper: test whether a python binary meets the minimum version
|
|
20
|
+
_python_version_ok() {
|
|
21
|
+
local py_bin="$1"
|
|
22
|
+
local ver_output
|
|
23
|
+
ver_output=$("$py_bin" --version 2>/dev/null) || return 1
|
|
24
|
+
# "Python 3.11.5" -> extract major.minor
|
|
25
|
+
local major minor
|
|
26
|
+
major=$(echo "$ver_output" | sed -E 's/Python ([0-9]+)\..*/\1/')
|
|
27
|
+
minor=$(echo "$ver_output" | sed -E 's/Python [0-9]+\.([0-9]+).*/\1/')
|
|
28
|
+
if [[ "$major" -gt "$required_major" ]] ||
|
|
29
|
+
{ [[ "$major" -eq "$required_major" ]] && [[ "$minor" -ge "$required_minor" ]]; }; then
|
|
30
|
+
return 0
|
|
31
|
+
fi
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# 1. Check default python3
|
|
36
|
+
if command -v python3 &>/dev/null && _python_version_ok python3; then
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# 2. Check common versioned binaries (Homebrew, system)
|
|
41
|
+
local py_bin
|
|
42
|
+
for py_bin in python3.13 python3.12 python3.11 python3.10; do
|
|
43
|
+
if command -v "$py_bin" &>/dev/null && _python_version_ok "$py_bin"; then
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
# 3. If uv is available, install Python 3.11 and retry
|
|
49
|
+
if command -v uv &>/dev/null; then
|
|
50
|
+
print_info "No Python >= 3.10 found. Installing Python 3.11 via uv..."
|
|
51
|
+
if uv python install 3.11; then
|
|
52
|
+
# uv installs to its managed path; check if python3.11 is now available
|
|
53
|
+
if command -v python3.11 &>/dev/null && _python_version_ok python3.11; then
|
|
54
|
+
print_success "Python 3.11 installed via uv"
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
# uv may have installed it but not on PATH — check uv's python path
|
|
58
|
+
local uv_py
|
|
59
|
+
uv_py=$(uv python find 3.11 2>/dev/null) || true
|
|
60
|
+
if [[ -n "$uv_py" ]] && _python_version_ok "$uv_py"; then
|
|
61
|
+
print_success "Python 3.11 installed via uv (at $uv_py)"
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
64
|
+
print_warning "uv installed Python 3.11 but it could not be found on PATH"
|
|
65
|
+
else
|
|
66
|
+
print_warning "uv python install 3.11 failed — see errors above"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# 4. No compatible Python found — emit clear error
|
|
71
|
+
local found_version="not installed"
|
|
72
|
+
if command -v python3 &>/dev/null; then
|
|
73
|
+
found_version=$(python3 --version 2>/dev/null || echo "unknown")
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
print_warning "cisco-ai-skill-scanner requires Python >= 3.10, but found: $found_version"
|
|
77
|
+
print_info "Fix options:"
|
|
78
|
+
print_info " 1. brew install python@3.11 (macOS)"
|
|
79
|
+
print_info " 2. uv python install 3.11 (cross-platform, recommended)"
|
|
80
|
+
print_info " 3. sudo apt install python3.11 (Debian/Ubuntu)"
|
|
81
|
+
print_info "After installing, re-run: aidevops update"
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
sanitize_plugin_namespace() {
|
|
86
|
+
local ns="$1"
|
|
87
|
+
# Strip any path components, keep only the final directory name
|
|
88
|
+
# This prevents ../../../etc/passwd and /absolute/paths
|
|
89
|
+
ns=$(basename "$ns")
|
|
90
|
+
# Additional safety: reject if it starts with . or contains suspicious chars
|
|
91
|
+
if [[ "$ns" =~ ^\.|\.\.|[[:space:]]|[\\/] ]]; then
|
|
92
|
+
return 1
|
|
93
|
+
fi
|
|
94
|
+
# Reject empty result
|
|
95
|
+
if [[ -z "$ns" ]]; then
|
|
96
|
+
return 1
|
|
97
|
+
fi
|
|
98
|
+
echo "$ns"
|
|
99
|
+
return 0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
deploy_plugins() {
|
|
103
|
+
local target_dir="$1"
|
|
104
|
+
local plugins_file="$2"
|
|
105
|
+
|
|
106
|
+
# Skip if no plugins.json or no jq
|
|
107
|
+
if [[ ! -f "$plugins_file" ]]; then
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
if ! command -v jq &>/dev/null; then
|
|
111
|
+
print_warning "jq not found; skipping plugin deployment"
|
|
112
|
+
return 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
local plugin_count
|
|
116
|
+
plugin_count=$(jq '.plugins | length' "$plugins_file" 2>/dev/null || echo "0")
|
|
117
|
+
if [[ "$plugin_count" -eq 0 ]]; then
|
|
118
|
+
return 0
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
local enabled_count
|
|
122
|
+
enabled_count=$(jq '[.plugins[] | select(.enabled != false)] | length' "$plugins_file" 2>/dev/null || echo "0")
|
|
123
|
+
if [[ "$enabled_count" -eq 0 ]]; then
|
|
124
|
+
print_info "No enabled plugins to deploy ($plugin_count configured, all disabled)"
|
|
125
|
+
return 0
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Remove directories for disabled plugins (cleanup)
|
|
129
|
+
local disabled_ns
|
|
130
|
+
local safe_ns
|
|
131
|
+
while IFS= read -r disabled_ns; do
|
|
132
|
+
[[ -z "$disabled_ns" ]] && continue
|
|
133
|
+
# Sanitize namespace to prevent path traversal
|
|
134
|
+
if ! safe_ns=$(sanitize_plugin_namespace "$disabled_ns"); then
|
|
135
|
+
print_warning " Skipping invalid plugin namespace: $disabled_ns"
|
|
136
|
+
continue
|
|
137
|
+
fi
|
|
138
|
+
if [[ -d "$target_dir/$safe_ns" ]]; then
|
|
139
|
+
rm -rf "${target_dir:?}/${safe_ns:?}"
|
|
140
|
+
print_info " Removed disabled plugin directory: $safe_ns"
|
|
141
|
+
fi
|
|
142
|
+
done < <(jq -r '.plugins[] | select(.enabled == false) | .namespace // empty' "$plugins_file" 2>/dev/null)
|
|
143
|
+
|
|
144
|
+
print_info "Deploying $enabled_count plugin(s)..."
|
|
145
|
+
|
|
146
|
+
local deployed=0
|
|
147
|
+
local failed=0
|
|
148
|
+
local skipped=0
|
|
149
|
+
|
|
150
|
+
# Process each enabled plugin
|
|
151
|
+
local safe_pns
|
|
152
|
+
while IFS=$'\t' read -r pname prepo pns pbranch; do
|
|
153
|
+
[[ -z "$pname" ]] && continue
|
|
154
|
+
pbranch="${pbranch:-main}"
|
|
155
|
+
|
|
156
|
+
# Sanitize namespace to prevent path traversal
|
|
157
|
+
if ! safe_pns=$(sanitize_plugin_namespace "$pns"); then
|
|
158
|
+
print_warning " Skipping plugin '$pname' with invalid namespace: $pns"
|
|
159
|
+
failed=$((failed + 1))
|
|
160
|
+
continue
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
local clone_dir="$target_dir/$safe_pns"
|
|
164
|
+
|
|
165
|
+
if [[ -d "$clone_dir" ]]; then
|
|
166
|
+
# Plugin directory exists — skip re-clone during setup
|
|
167
|
+
# Users can force update via: aidevops plugin update [name]
|
|
168
|
+
skipped=$((skipped + 1))
|
|
169
|
+
continue
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Clone plugin repo
|
|
173
|
+
print_info " Installing plugin '$pname' ($prepo)..."
|
|
174
|
+
if git clone --branch "$pbranch" --depth 1 "$prepo" "$clone_dir" 2>/dev/null; then
|
|
175
|
+
# Remove .git directory (tracked via plugins.json, not nested git)
|
|
176
|
+
rm -rf "$clone_dir/.git"
|
|
177
|
+
# Set permissions on any scripts
|
|
178
|
+
if [[ -d "$clone_dir/scripts" ]]; then
|
|
179
|
+
chmod +x "$clone_dir/scripts/"*.sh 2>/dev/null || true
|
|
180
|
+
fi
|
|
181
|
+
deployed=$((deployed + 1))
|
|
182
|
+
else
|
|
183
|
+
print_warning " Failed to install plugin '$pname' (network or auth issue)"
|
|
184
|
+
failed=$((failed + 1))
|
|
185
|
+
fi
|
|
186
|
+
done < <(jq -r '.plugins[] | select(.enabled != false) | [.name, .repo, .namespace, (.branch // "main")] | @tsv' "$plugins_file" 2>/dev/null)
|
|
187
|
+
|
|
188
|
+
# Summary
|
|
189
|
+
if [[ "$deployed" -gt 0 ]]; then
|
|
190
|
+
print_success "Deployed $deployed plugin(s)"
|
|
191
|
+
fi
|
|
192
|
+
if [[ "$skipped" -gt 0 ]]; then
|
|
193
|
+
print_info "$skipped plugin(s) already deployed (use 'aidevops plugin update' to refresh)"
|
|
194
|
+
fi
|
|
195
|
+
if [[ "$failed" -gt 0 ]]; then
|
|
196
|
+
print_warning "$failed plugin(s) failed to deploy (non-blocking)"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
return 0
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
generate_agent_skills() {
|
|
203
|
+
print_info "Generating Agent Skills SKILL.md files..."
|
|
204
|
+
|
|
205
|
+
local skills_script="$HOME/.aidevops/agents/scripts/generate-skills.sh"
|
|
206
|
+
|
|
207
|
+
if [[ -f "$skills_script" ]]; then
|
|
208
|
+
if bash "$skills_script" 2>/dev/null; then
|
|
209
|
+
print_success "Agent Skills SKILL.md files generated"
|
|
210
|
+
else
|
|
211
|
+
print_warning "Agent Skills generation encountered issues (non-critical)"
|
|
212
|
+
fi
|
|
213
|
+
else
|
|
214
|
+
print_warning "Agent Skills generator not found at $skills_script"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
return 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
create_skill_symlinks() {
|
|
221
|
+
print_info "Creating symlinks for imported skills..."
|
|
222
|
+
|
|
223
|
+
local skill_sources="$HOME/.aidevops/agents/configs/skill-sources.json"
|
|
224
|
+
local agents_dir="$HOME/.aidevops/agents"
|
|
225
|
+
|
|
226
|
+
# Skip if no skill-sources.json or jq not available
|
|
227
|
+
if [[ ! -f "$skill_sources" ]]; then
|
|
228
|
+
print_info "No imported skills found (skill-sources.json not present)"
|
|
229
|
+
return 0
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
if ! command -v jq &>/dev/null; then
|
|
233
|
+
print_warning "jq not found - cannot create skill symlinks"
|
|
234
|
+
return 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# Check if there are any skills
|
|
238
|
+
local skill_count
|
|
239
|
+
skill_count=$(jq '.skills | length' "$skill_sources" 2>/dev/null || echo "0")
|
|
240
|
+
|
|
241
|
+
if [[ "$skill_count" -eq 0 ]]; then
|
|
242
|
+
print_info "No imported skills to symlink"
|
|
243
|
+
return 0
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# AI assistant skill directories
|
|
247
|
+
local skill_dirs=(
|
|
248
|
+
"$HOME/.config/opencode/skills"
|
|
249
|
+
"$HOME/.codex/skills"
|
|
250
|
+
"$HOME/.claude/skills"
|
|
251
|
+
"$HOME/.config/amp/tools"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Create skill directories if they don't exist
|
|
255
|
+
for dir in "${skill_dirs[@]}"; do
|
|
256
|
+
mkdir -p "$dir" 2>/dev/null || true
|
|
257
|
+
done
|
|
258
|
+
|
|
259
|
+
local created_count=0
|
|
260
|
+
|
|
261
|
+
# Read each skill and create symlinks
|
|
262
|
+
while IFS= read -r skill_json; do
|
|
263
|
+
local name local_path
|
|
264
|
+
name=$(echo "$skill_json" | jq -r '.name')
|
|
265
|
+
local_path=$(echo "$skill_json" | jq -r '.local_path')
|
|
266
|
+
|
|
267
|
+
# Skip if path doesn't exist
|
|
268
|
+
local full_path="$agents_dir/${local_path#.agents/}"
|
|
269
|
+
if [[ ! -f "$full_path" ]]; then
|
|
270
|
+
print_warning "Skill file not found: $full_path"
|
|
271
|
+
continue
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# Create symlinks in each AI assistant directory
|
|
275
|
+
for skill_dir in "${skill_dirs[@]}"; do
|
|
276
|
+
local target_file
|
|
277
|
+
|
|
278
|
+
# Amp expects <name>.md directly, others expect <name>/SKILL.md
|
|
279
|
+
if [[ "$skill_dir" == *"/amp/tools" ]]; then
|
|
280
|
+
target_file="$skill_dir/${name}.md"
|
|
281
|
+
else
|
|
282
|
+
local target_dir="$skill_dir/$name"
|
|
283
|
+
target_file="$target_dir/SKILL.md"
|
|
284
|
+
# Create skill subdirectory
|
|
285
|
+
mkdir -p "$target_dir" 2>/dev/null || continue
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
# Create symlink (remove existing first)
|
|
289
|
+
rm -f "$target_file" 2>/dev/null || true
|
|
290
|
+
if ln -sf "$full_path" "$target_file" 2>/dev/null; then
|
|
291
|
+
((++created_count))
|
|
292
|
+
fi
|
|
293
|
+
done
|
|
294
|
+
done < <(jq -c '.skills[]' "$skill_sources" 2>/dev/null)
|
|
295
|
+
|
|
296
|
+
if [[ $created_count -gt 0 ]]; then
|
|
297
|
+
print_success "Created $created_count skill symlinks across AI assistants"
|
|
298
|
+
else
|
|
299
|
+
print_info "No skill symlinks created"
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
return 0
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
check_skill_updates() {
|
|
306
|
+
print_info "Checking for skill updates..."
|
|
307
|
+
|
|
308
|
+
local skill_sources="$HOME/.aidevops/agents/configs/skill-sources.json"
|
|
309
|
+
|
|
310
|
+
# Skip if no skill-sources.json or required tools not available
|
|
311
|
+
if [[ ! -f "$skill_sources" ]]; then
|
|
312
|
+
print_info "No imported skills to check"
|
|
313
|
+
return 0
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
if ! command -v jq &>/dev/null; then
|
|
317
|
+
print_warning "jq not found - cannot check skill updates"
|
|
318
|
+
return 0
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
if ! command -v curl &>/dev/null; then
|
|
322
|
+
print_warning "curl not found - cannot check skill updates"
|
|
323
|
+
return 0
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
local skill_count
|
|
327
|
+
skill_count=$(jq '.skills | length' "$skill_sources" 2>/dev/null || echo "0")
|
|
328
|
+
|
|
329
|
+
if [[ "$skill_count" -eq 0 ]]; then
|
|
330
|
+
print_info "No imported skills to check"
|
|
331
|
+
return 0
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
local updates_available=0
|
|
335
|
+
local update_list=""
|
|
336
|
+
|
|
337
|
+
# Check each skill for updates
|
|
338
|
+
while IFS= read -r skill_json; do
|
|
339
|
+
local name upstream_url upstream_commit
|
|
340
|
+
name=$(echo "$skill_json" | jq -r '.name')
|
|
341
|
+
upstream_url=$(echo "$skill_json" | jq -r '.upstream_url')
|
|
342
|
+
upstream_commit=$(echo "$skill_json" | jq -r '.upstream_commit // empty')
|
|
343
|
+
|
|
344
|
+
# Skip skills without upstream URL or commit (e.g., context7 imports)
|
|
345
|
+
if [[ -z "$upstream_url" || "$upstream_url" == "null" ]]; then
|
|
346
|
+
continue
|
|
347
|
+
fi
|
|
348
|
+
if [[ -z "$upstream_commit" ]]; then
|
|
349
|
+
continue
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
# Extract owner/repo from GitHub URL
|
|
353
|
+
local owner_repo
|
|
354
|
+
owner_repo=$(echo "$upstream_url" | sed -E 's|https://github.com/||; s|\.git$||; s|/tree/.*||')
|
|
355
|
+
|
|
356
|
+
if [[ -z "$owner_repo" || ! "$owner_repo" =~ / ]]; then
|
|
357
|
+
continue
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
# Get latest commit from GitHub API (silent, with timeout)
|
|
361
|
+
local api_response latest_commit
|
|
362
|
+
api_response=$(curl -s --max-time 5 "https://api.github.com/repos/$owner_repo/commits?per_page=1" 2>/dev/null)
|
|
363
|
+
|
|
364
|
+
# Check if response is an array (success) or object (error like rate limit)
|
|
365
|
+
if echo "$api_response" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
366
|
+
latest_commit=$(echo "$api_response" | jq -r '.[0].sha // empty')
|
|
367
|
+
else
|
|
368
|
+
# API returned error object, skip this skill
|
|
369
|
+
continue
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
if [[ -n "$latest_commit" && "$latest_commit" != "$upstream_commit" ]]; then
|
|
373
|
+
((++updates_available))
|
|
374
|
+
update_list="${update_list}\n - $name (${upstream_commit:0:7} → ${latest_commit:0:7})"
|
|
375
|
+
fi
|
|
376
|
+
done < <(jq -c '.skills[]' "$skill_sources" 2>/dev/null)
|
|
377
|
+
|
|
378
|
+
if [[ $updates_available -gt 0 ]]; then
|
|
379
|
+
print_warning "Skill updates available:$update_list"
|
|
380
|
+
print_info "Run: ~/.aidevops/agents/scripts/add-skill-helper.sh check-updates"
|
|
381
|
+
print_info "To update a skill: ~/.aidevops/agents/scripts/add-skill-helper.sh add <url> --force"
|
|
382
|
+
else
|
|
383
|
+
print_success "All imported skills are up to date"
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
return 0
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
scan_imported_skills() {
|
|
390
|
+
print_info "Running security scan on imported skills..."
|
|
391
|
+
|
|
392
|
+
local security_helper="$HOME/.aidevops/agents/scripts/security-helper.sh"
|
|
393
|
+
|
|
394
|
+
if [[ ! -f "$security_helper" ]]; then
|
|
395
|
+
print_warning "security-helper.sh not found - skipping skill scan"
|
|
396
|
+
return 0
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# Install skill-scanner if not present
|
|
400
|
+
# Pre-check: cisco-ai-skill-scanner requires Python >= 3.10
|
|
401
|
+
# Fallback chain: uv -> pipx -> venv+symlink -> pip3 --user (legacy)
|
|
402
|
+
# PEP 668 (Ubuntu 24.04+) blocks pip3 --user, so we try isolated methods first
|
|
403
|
+
if ! command -v skill-scanner &>/dev/null; then
|
|
404
|
+
# Verify Python >= 3.10 is available (or install it via uv)
|
|
405
|
+
if ! check_python_for_skill_scanner; then
|
|
406
|
+
print_warning "Skipping Cisco Skill Scanner install (Python >= 3.10 required)"
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
local installed=false
|
|
411
|
+
|
|
412
|
+
# 1. uv tool install (preferred - fast, isolated, manages its own Python)
|
|
413
|
+
if [[ "$installed" == "false" ]] && command -v uv &>/dev/null; then
|
|
414
|
+
print_info "Installing Cisco Skill Scanner via uv..."
|
|
415
|
+
if run_with_spinner "Installing cisco-ai-skill-scanner" uv tool install cisco-ai-skill-scanner; then
|
|
416
|
+
print_success "Cisco Skill Scanner installed via uv"
|
|
417
|
+
installed=true
|
|
418
|
+
fi
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
# 2. pipx install (designed for isolated app installs)
|
|
422
|
+
if [[ "$installed" == "false" ]] && command -v pipx &>/dev/null; then
|
|
423
|
+
print_info "Installing Cisco Skill Scanner via pipx..."
|
|
424
|
+
if run_with_spinner "Installing cisco-ai-skill-scanner" pipx install cisco-ai-skill-scanner; then
|
|
425
|
+
print_success "Cisco Skill Scanner installed via pipx"
|
|
426
|
+
installed=true
|
|
427
|
+
fi
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
# 3. venv + symlink (works on PEP 668 systems without uv/pipx)
|
|
431
|
+
if [[ "$installed" == "false" ]] && command -v python3 &>/dev/null; then
|
|
432
|
+
local venv_dir="$HOME/.aidevops/.agent-workspace/work/cisco-scanner-env"
|
|
433
|
+
local bin_dir="$HOME/.local/bin"
|
|
434
|
+
print_info "Installing Cisco Skill Scanner in isolated venv..."
|
|
435
|
+
if python3 -m venv "$venv_dir" 2>/dev/null &&
|
|
436
|
+
"$venv_dir/bin/pip" install cisco-ai-skill-scanner 2>/dev/null; then
|
|
437
|
+
mkdir -p "$bin_dir"
|
|
438
|
+
ln -sf "$venv_dir/bin/skill-scanner" "$bin_dir/skill-scanner"
|
|
439
|
+
print_success "Cisco Skill Scanner installed via venv ($venv_dir)"
|
|
440
|
+
installed=true
|
|
441
|
+
else
|
|
442
|
+
rm -rf "$venv_dir" 2>/dev/null || true
|
|
443
|
+
fi
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
# 4. pip3 --user (legacy fallback, fails on PEP 668 systems)
|
|
447
|
+
if [[ "$installed" == "false" ]] && command -v pip3 &>/dev/null; then
|
|
448
|
+
print_info "Installing Cisco Skill Scanner via pip3 --user..."
|
|
449
|
+
if run_with_spinner "Installing cisco-ai-skill-scanner" pip3 install --user cisco-ai-skill-scanner 2>/dev/null; then
|
|
450
|
+
print_success "Cisco Skill Scanner installed via pip3"
|
|
451
|
+
installed=true
|
|
452
|
+
fi
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
if [[ "$installed" == "false" ]]; then
|
|
456
|
+
print_warning "Failed to install Cisco Skill Scanner - skipping security scan"
|
|
457
|
+
print_info "Install manually with: uv tool install cisco-ai-skill-scanner"
|
|
458
|
+
print_info "Or: pipx install cisco-ai-skill-scanner"
|
|
459
|
+
return 0
|
|
460
|
+
fi
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
if bash "$security_helper" skill-scan all 2>/dev/null; then
|
|
464
|
+
print_success "All imported skills passed security scan"
|
|
465
|
+
else
|
|
466
|
+
print_warning "Some imported skills have security findings - review with: aidevops skill scan"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
return 0
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
setup_multi_tenant_credentials() {
|
|
473
|
+
print_info "Multi-tenant credential storage..."
|
|
474
|
+
|
|
475
|
+
local credential_helper="$HOME/.aidevops/agents/scripts/credential-helper.sh"
|
|
476
|
+
|
|
477
|
+
if [[ ! -f "$credential_helper" ]]; then
|
|
478
|
+
# Try local script if deployed version not available yet
|
|
479
|
+
credential_helper=".agents/scripts/credential-helper.sh"
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
if [[ ! -f "$credential_helper" ]]; then
|
|
483
|
+
print_warning "credential-helper.sh not found - skipping"
|
|
484
|
+
return 0
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
# Check if already initialized
|
|
488
|
+
if [[ -d "$HOME/.config/aidevops/tenants" ]]; then
|
|
489
|
+
local tenant_count
|
|
490
|
+
tenant_count=$(find "$HOME/.config/aidevops/tenants" -maxdepth 1 -type d | wc -l)
|
|
491
|
+
# Subtract 1 for the tenants/ dir itself
|
|
492
|
+
tenant_count=$((tenant_count - 1))
|
|
493
|
+
print_success "Multi-tenant already initialized ($tenant_count tenant(s))"
|
|
494
|
+
bash "$credential_helper" status
|
|
495
|
+
return 0
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
# Check if there are existing credentials to migrate
|
|
499
|
+
if [[ -f "$HOME/.config/aidevops/credentials.sh" ]]; then
|
|
500
|
+
local key_count
|
|
501
|
+
key_count=$(grep -c "^export " "$HOME/.config/aidevops/credentials.sh" 2>/dev/null || echo "0")
|
|
502
|
+
print_info "Found $key_count existing API keys in credentials.sh"
|
|
503
|
+
print_info "Multi-tenant enables managing separate credential sets for:"
|
|
504
|
+
echo " - Multiple clients (agency/freelance work)"
|
|
505
|
+
echo " - Multiple environments (production, staging)"
|
|
506
|
+
echo " - Multiple accounts (personal, work)"
|
|
507
|
+
echo ""
|
|
508
|
+
print_info "Your existing keys will be migrated to a 'default' tenant."
|
|
509
|
+
print_info "Everything continues to work as before - this is non-breaking."
|
|
510
|
+
echo ""
|
|
511
|
+
|
|
512
|
+
read -r -p "Enable multi-tenant credential storage? [Y/n]: " enable_mt
|
|
513
|
+
enable_mt=$(echo "$enable_mt" | tr '[:upper:]' '[:lower:]')
|
|
514
|
+
|
|
515
|
+
if [[ "$enable_mt" =~ ^[Yy]?$ || "$enable_mt" == "yes" ]]; then
|
|
516
|
+
bash "$credential_helper" init
|
|
517
|
+
print_success "Multi-tenant credential storage enabled"
|
|
518
|
+
echo ""
|
|
519
|
+
print_info "Quick start:"
|
|
520
|
+
echo " credential-helper.sh create client-name # Create a tenant"
|
|
521
|
+
echo " credential-helper.sh switch client-name # Switch active tenant"
|
|
522
|
+
echo " credential-helper.sh set KEY val --tenant X # Add key to tenant"
|
|
523
|
+
echo " credential-helper.sh status # Show current state"
|
|
524
|
+
else
|
|
525
|
+
print_info "Skipped. Enable later: credential-helper.sh init"
|
|
526
|
+
fi
|
|
527
|
+
else
|
|
528
|
+
print_info "No existing credentials found. Multi-tenant available when needed."
|
|
529
|
+
print_info "Enable later: credential-helper.sh init"
|
|
530
|
+
fi
|
|
531
|
+
|
|
532
|
+
return 0
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
check_tool_updates() {
|
|
536
|
+
print_info "Checking for tool updates..."
|
|
537
|
+
|
|
538
|
+
local tool_check_script="$HOME/.aidevops/agents/scripts/tool-version-check.sh"
|
|
539
|
+
|
|
540
|
+
if [[ ! -f "$tool_check_script" ]]; then
|
|
541
|
+
# Try local script if deployed version not available yet
|
|
542
|
+
tool_check_script=".agents/scripts/tool-version-check.sh"
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
if [[ ! -f "$tool_check_script" ]]; then
|
|
546
|
+
print_warning "Tool version check script not found - skipping update check"
|
|
547
|
+
return 0
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
# Run the check in quiet mode first to see if there are updates
|
|
551
|
+
# Capture both output and exit code
|
|
552
|
+
local outdated_output
|
|
553
|
+
local check_exit_code
|
|
554
|
+
outdated_output=$(bash "$tool_check_script" --quiet 2>&1) || check_exit_code=$?
|
|
555
|
+
check_exit_code=${check_exit_code:-0}
|
|
556
|
+
|
|
557
|
+
# If the script failed, warn and continue
|
|
558
|
+
if [[ $check_exit_code -ne 0 ]]; then
|
|
559
|
+
print_warning "Tool version check encountered an error (exit code: $check_exit_code)"
|
|
560
|
+
print_info "Run 'aidevops update-tools' manually to check for updates"
|
|
561
|
+
return 0
|
|
562
|
+
fi
|
|
563
|
+
|
|
564
|
+
if [[ -z "$outdated_output" ]]; then
|
|
565
|
+
print_success "All tools are up to date!"
|
|
566
|
+
return 0
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
# Show what's outdated
|
|
570
|
+
echo ""
|
|
571
|
+
print_warning "Some tools have updates available:"
|
|
572
|
+
echo ""
|
|
573
|
+
bash "$tool_check_script" --quiet
|
|
574
|
+
echo ""
|
|
575
|
+
|
|
576
|
+
read -r -p "Update all outdated tools now? [Y/n]: " do_update
|
|
577
|
+
|
|
578
|
+
if [[ "$do_update" =~ ^[Yy]?$ || "$do_update" == "Y" ]]; then
|
|
579
|
+
print_info "Updating tools..."
|
|
580
|
+
bash "$tool_check_script" --update
|
|
581
|
+
print_success "Tool updates complete!"
|
|
582
|
+
else
|
|
583
|
+
print_info "Skipped tool updates"
|
|
584
|
+
print_info "Run 'aidevops update-tools' anytime to update tools"
|
|
585
|
+
fi
|
|
586
|
+
|
|
587
|
+
return 0
|
|
588
|
+
}
|