aidevops 2.106.0 → 2.107.0
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 +37 -28
- package/VERSION +1 -1
- package/aidevops.sh +568 -8
- package/package.json +1 -1
- package/setup.sh +128 -10
package/README.md
CHANGED
|
@@ -92,10 +92,10 @@ The result: AI agents that work *with* your development process, not around it.
|
|
|
92
92
|
|
|
93
93
|
### Agent Structure
|
|
94
94
|
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
95
|
+
- 11 primary agents (Build+, SEO, Marketing, etc.) with specialist @subagents on demand
|
|
96
|
+
- 735+ subagent markdown files organized by domain
|
|
97
|
+
- 194 helper scripts in `.agents/scripts/`
|
|
98
|
+
- 53 slash commands for common workflows
|
|
99
99
|
|
|
100
100
|
<!-- AI-CONTEXT-END -->
|
|
101
101
|
|
|
@@ -453,7 +453,7 @@ aidevops implements proven agent design patterns identified by [Lance Martin (La
|
|
|
453
453
|
|
|
454
454
|
| Pattern | Description | aidevops Implementation |
|
|
455
455
|
|---------|-------------|------------------------|
|
|
456
|
-
| **Give Agents a Computer** | Filesystem + shell for persistent context | `~/.aidevops/.agent-workspace/`,
|
|
456
|
+
| **Give Agents a Computer** | Filesystem + shell for persistent context | `~/.aidevops/.agent-workspace/`, 194 helper scripts |
|
|
457
457
|
| **Multi-Layer Action Space** | Few tools, push actions to computer | Per-agent MCP filtering (~12-20 tools each) |
|
|
458
458
|
| **Progressive Disclosure** | Load context on-demand | Subagent routing with content summaries, YAML frontmatter, read-on-demand |
|
|
459
459
|
| **Offload Context** | Write results to filesystem | `.agent-workspace/work/[project]/` for persistence |
|
|
@@ -465,6 +465,8 @@ aidevops implements proven agent design patterns identified by [Lance Martin (La
|
|
|
465
465
|
| **Evolve Context** | Learn from sessions | `/remember`, `/recall` with SQLite FTS5 + opt-in semantic search |
|
|
466
466
|
| **Pattern Tracking** | Learn what works/fails | `pattern-tracker-helper.sh`, `/patterns` command |
|
|
467
467
|
| **Cost-Aware Routing** | Match model to task complexity | `model-routing.md` with 5-tier guidance, `/route` command |
|
|
468
|
+
| **Model Comparison** | Compare models side-by-side | `/compare-models` (live data), `/compare-models-free` (offline) |
|
|
469
|
+
| **Response Scoring** | Evaluate actual model outputs | `/score-responses` with structured criteria |
|
|
468
470
|
|
|
469
471
|
**Key insight**: Context is a finite resource with diminishing returns. aidevops treats every token as precious - loading only what's needed, when it's needed.
|
|
470
472
|
|
|
@@ -1406,7 +1408,7 @@ aidevops is registered as a **Claude Code plugin marketplace**. Install with two
|
|
|
1406
1408
|
/plugin install aidevops@aidevops
|
|
1407
1409
|
```
|
|
1408
1410
|
|
|
1409
|
-
This installs the complete framework:
|
|
1411
|
+
This installs the complete framework: 11 primary agents, 735+ subagents, and 194 helper scripts.
|
|
1410
1412
|
|
|
1411
1413
|
### Importing External Skills
|
|
1412
1414
|
|
|
@@ -1472,29 +1474,27 @@ Call them in your AI assistant conversation with a simple @mention
|
|
|
1472
1474
|
|
|
1473
1475
|
### **Main Agents**
|
|
1474
1476
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
| Name | File | Purpose |
|
|
1478
|
-
|
|
1479
|
-
|
|
|
1480
|
-
|
|
|
1481
|
-
|
|
|
1482
|
-
|
|
|
1483
|
-
|
|
|
1484
|
-
|
|
|
1485
|
-
|
|
|
1486
|
-
|
|
|
1487
|
-
|
|
|
1488
|
-
|
|
|
1489
|
-
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
| Video | `video.md` | AI video generation, prompt engineering, programmatic video | augment |
|
|
1493
|
-
| WordPress | `wordpress.md` | WordPress ecosystem (dev, admin, MainWP, LocalWP) | localwp, context7, augment |
|
|
1477
|
+
Primary agents as registered in `subagent-index.toon` (11 total). MCPs are loaded on-demand per subagent, not per primary agent:
|
|
1478
|
+
|
|
1479
|
+
| Name | File | Purpose | Model Tier |
|
|
1480
|
+
|------|------|---------|------------|
|
|
1481
|
+
| Build+ | `build-plus.md` | Enhanced Build with context tools (default agent) | opus |
|
|
1482
|
+
| Accounts | `accounts.md` | Financial operations | opus |
|
|
1483
|
+
| Content | `content.md` | Content creation workflows | opus |
|
|
1484
|
+
| Health | `health.md` | Health and wellness | opus |
|
|
1485
|
+
| Legal | `legal.md` | Legal compliance | opus |
|
|
1486
|
+
| Marketing | `marketing.md` | Marketing strategy and email campaigns | opus |
|
|
1487
|
+
| Research | `research.md` | Research and analysis tasks | gemini/grok |
|
|
1488
|
+
| Sales | `sales.md` | Sales operations and CRM pipeline | opus |
|
|
1489
|
+
| SEO | `seo.md` | SEO optimization and analysis | opus |
|
|
1490
|
+
| Social-Media | `social-media.md` | Social media management | opus |
|
|
1491
|
+
| Video | `video.md` | AI video generation and prompt engineering | opus |
|
|
1492
|
+
|
|
1493
|
+
**Specialist subagents** (@plan-plus, @aidevops, @wordpress, Build-Agent, Build-MCP, etc.) live under `tools/` or as `mode: subagent` files and are invoked via @mention when domain expertise is needed. See `subagent-index.toon` for the full listing.
|
|
1494
1494
|
|
|
1495
1495
|
### **Example Subagents with MCP Integration**
|
|
1496
1496
|
|
|
1497
|
-
These are examples of subagents that have supporting MCPs enabled. See `.agents/` for the full list of
|
|
1497
|
+
These are examples of subagents that have supporting MCPs enabled. See `.agents/` for the full list of 735+ subagents organized by domain.
|
|
1498
1498
|
|
|
1499
1499
|
| Agent | Purpose | MCPs Enabled |
|
|
1500
1500
|
|-------|---------|--------------|
|
|
@@ -1712,6 +1712,15 @@ Configure time tracking per-repo via `.aidevops.json`.
|
|
|
1712
1712
|
| `/runners` | Batch dispatch tasks to parallel agents (task IDs, PR URLs, or descriptions) |
|
|
1713
1713
|
| `/log-issue-aidevops` | Report issues with aidevops (gathers diagnostics, checks duplicates, creates GitHub issue) |
|
|
1714
1714
|
|
|
1715
|
+
**AI Model Comparison**:
|
|
1716
|
+
|
|
1717
|
+
| Command | Purpose |
|
|
1718
|
+
|---------|---------|
|
|
1719
|
+
| `/compare-models` | Compare AI models by pricing, context, capabilities (with live web data) |
|
|
1720
|
+
| `/compare-models-free` | Compare AI models using offline embedded data only (no web fetches) |
|
|
1721
|
+
| `/score-responses` | Score and compare actual model responses with structured criteria |
|
|
1722
|
+
| `/route` | Suggest optimal model tier for a task description |
|
|
1723
|
+
|
|
1715
1724
|
### Ralph Loop - Iterative AI Development
|
|
1716
1725
|
|
|
1717
1726
|
The **Ralph Loop** (named after Ralph Wiggum's persistent optimism) enables autonomous iterative development. The AI keeps working on a task until it's complete, automatically resolving issues that arise.
|
|
@@ -2128,8 +2137,8 @@ aidevops/
|
|
|
2128
2137
|
├── AGENTS.md # AI agent guidance (dev)
|
|
2129
2138
|
├── .agents/ # Agents and documentation
|
|
2130
2139
|
│ ├── AGENTS.md # User guide (deployed to ~/.aidevops/agents/)
|
|
2131
|
-
│ ├── *.md #
|
|
2132
|
-
│ ├── scripts/ #
|
|
2140
|
+
│ ├── *.md # 11 primary agents
|
|
2141
|
+
│ ├── scripts/ # 194 helper scripts
|
|
2133
2142
|
│ ├── tools/ # Cross-domain utilities (video, browser, git, etc.)
|
|
2134
2143
|
│ ├── services/ # External service integrations
|
|
2135
2144
|
│ └── workflows/ # Development process guides
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.107.0
|
package/aidevops.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# AI DevOps Framework CLI
|
|
4
4
|
# Usage: aidevops <command> [options]
|
|
5
5
|
#
|
|
6
|
-
# Version: 2.
|
|
6
|
+
# Version: 2.107.0
|
|
7
7
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
|
|
@@ -851,6 +851,7 @@ cmd_init() {
|
|
|
851
851
|
local enable_time_tracking=false
|
|
852
852
|
local enable_database=false
|
|
853
853
|
local enable_beads=false
|
|
854
|
+
local enable_sops=false
|
|
854
855
|
|
|
855
856
|
case "$features" in
|
|
856
857
|
all)
|
|
@@ -881,6 +882,9 @@ cmd_init() {
|
|
|
881
882
|
enable_beads=true
|
|
882
883
|
enable_planning=true # beads requires planning
|
|
883
884
|
;;
|
|
885
|
+
sops)
|
|
886
|
+
enable_sops=true
|
|
887
|
+
;;
|
|
884
888
|
*)
|
|
885
889
|
# Comma-separated list
|
|
886
890
|
IFS=',' read -ra FEATURE_LIST <<< "$features"
|
|
@@ -898,6 +902,7 @@ cmd_init() {
|
|
|
898
902
|
enable_beads=true
|
|
899
903
|
enable_planning=true
|
|
900
904
|
;;
|
|
905
|
+
sops) enable_sops=true ;;
|
|
901
906
|
esac
|
|
902
907
|
done
|
|
903
908
|
;;
|
|
@@ -933,13 +938,31 @@ cmd_init() {
|
|
|
933
938
|
"seeds_path": "seeds",
|
|
934
939
|
"auto_generate_migration": true
|
|
935
940
|
},
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
+
"beads": {
|
|
942
|
+
"enabled": $enable_beads,
|
|
943
|
+
"sync_on_commit": false,
|
|
944
|
+
"auto_ready_check": true
|
|
945
|
+
},
|
|
946
|
+
"sops": {
|
|
947
|
+
"enabled": $enable_sops,
|
|
948
|
+
"backend": "age",
|
|
949
|
+
"patterns": ["*.secret.yaml", "*.secret.json", "configs/*.enc.json", "configs/*.enc.yaml"]
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
"plugins": []
|
|
941
953
|
}
|
|
942
954
|
EOF
|
|
955
|
+
# Note: plugins array is always present but empty by default.
|
|
956
|
+
# Users add plugins via: aidevops plugin add <repo-url> [--namespace <name>]
|
|
957
|
+
# Schema per plugin entry:
|
|
958
|
+
# {
|
|
959
|
+
# "name": "pro",
|
|
960
|
+
# "repo": "https://github.com/user/aidevops-pro.git",
|
|
961
|
+
# "branch": "main",
|
|
962
|
+
# "namespace": "pro",
|
|
963
|
+
# "enabled": true
|
|
964
|
+
# }
|
|
965
|
+
# Plugins deploy to ~/.aidevops/agents/<namespace>/ (namespaced, no collisions)
|
|
943
966
|
print_success "Created .aidevops.json"
|
|
944
967
|
|
|
945
968
|
# Create .agents symlink (or migrate from legacy .agent)
|
|
@@ -1109,6 +1132,77 @@ EOF
|
|
|
1109
1132
|
fi
|
|
1110
1133
|
fi
|
|
1111
1134
|
|
|
1135
|
+
# Initialize SOPS if enabled
|
|
1136
|
+
if [[ "$enable_sops" == "true" ]]; then
|
|
1137
|
+
print_info "Setting up SOPS encrypted config support..."
|
|
1138
|
+
|
|
1139
|
+
# Check for sops and age
|
|
1140
|
+
local sops_ready=true
|
|
1141
|
+
if ! command -v sops &>/dev/null; then
|
|
1142
|
+
print_warning "SOPS not installed"
|
|
1143
|
+
echo " Install with: brew install sops"
|
|
1144
|
+
sops_ready=false
|
|
1145
|
+
fi
|
|
1146
|
+
if ! command -v age-keygen &>/dev/null; then
|
|
1147
|
+
print_warning "age not installed (default SOPS backend)"
|
|
1148
|
+
echo " Install with: brew install age"
|
|
1149
|
+
sops_ready=false
|
|
1150
|
+
fi
|
|
1151
|
+
|
|
1152
|
+
# Generate age key if none exists
|
|
1153
|
+
local age_key_file="$HOME/.config/sops/age/keys.txt"
|
|
1154
|
+
if [[ "$sops_ready" == "true" ]] && [[ ! -f "$age_key_file" ]]; then
|
|
1155
|
+
print_info "Generating age key for SOPS..."
|
|
1156
|
+
mkdir -p "$(dirname "$age_key_file")"
|
|
1157
|
+
age-keygen -o "$age_key_file" 2>/dev/null
|
|
1158
|
+
chmod 600 "$age_key_file"
|
|
1159
|
+
print_success "Age key generated at $age_key_file"
|
|
1160
|
+
fi
|
|
1161
|
+
|
|
1162
|
+
# Create .sops.yaml if it doesn't exist
|
|
1163
|
+
if [[ ! -f "$project_root/.sops.yaml" ]]; then
|
|
1164
|
+
local age_pubkey=""
|
|
1165
|
+
if [[ -f "$age_key_file" ]]; then
|
|
1166
|
+
age_pubkey=$(grep -o 'age1[a-z0-9]*' "$age_key_file" | head -1)
|
|
1167
|
+
fi
|
|
1168
|
+
|
|
1169
|
+
if [[ -n "$age_pubkey" ]]; then
|
|
1170
|
+
cat > "$project_root/.sops.yaml" << SOPSEOF
|
|
1171
|
+
# SOPS configuration - encrypts values in config files while keeping keys visible
|
|
1172
|
+
# See: .agents/tools/credentials/sops.md
|
|
1173
|
+
creation_rules:
|
|
1174
|
+
- path_regex: '\.secret\.(yaml|yml|json)$'
|
|
1175
|
+
age: >-
|
|
1176
|
+
$age_pubkey
|
|
1177
|
+
- path_regex: 'configs/.*\.enc\.(yaml|yml|json)$'
|
|
1178
|
+
age: >-
|
|
1179
|
+
$age_pubkey
|
|
1180
|
+
SOPSEOF
|
|
1181
|
+
print_success "Created .sops.yaml with age key"
|
|
1182
|
+
else
|
|
1183
|
+
cat > "$project_root/.sops.yaml" << 'SOPSEOF'
|
|
1184
|
+
# SOPS configuration - encrypts values in config files while keeping keys visible
|
|
1185
|
+
# See: .agents/tools/credentials/sops.md
|
|
1186
|
+
#
|
|
1187
|
+
# Generate an age key first:
|
|
1188
|
+
# age-keygen -o ~/.config/sops/age/keys.txt
|
|
1189
|
+
#
|
|
1190
|
+
# Then replace AGE_PUBLIC_KEY below with your public key:
|
|
1191
|
+
creation_rules:
|
|
1192
|
+
- path_regex: '\.secret\.(yaml|yml|json)$'
|
|
1193
|
+
age: >-
|
|
1194
|
+
AGE_PUBLIC_KEY
|
|
1195
|
+
- path_regex: 'configs/.*\.enc\.(yaml|yml|json)$'
|
|
1196
|
+
age: >-
|
|
1197
|
+
AGE_PUBLIC_KEY
|
|
1198
|
+
SOPSEOF
|
|
1199
|
+
print_warning "Created .sops.yaml template (replace AGE_PUBLIC_KEY with your key)"
|
|
1200
|
+
fi
|
|
1201
|
+
else
|
|
1202
|
+
print_info ".sops.yaml already exists"
|
|
1203
|
+
fi
|
|
1204
|
+
fi
|
|
1205
|
+
|
|
1112
1206
|
# Add to .gitignore if needed
|
|
1113
1207
|
local gitignore="$project_root/.gitignore"
|
|
1114
1208
|
if [[ -f "$gitignore" ]]; then
|
|
@@ -1159,6 +1253,7 @@ EOF
|
|
|
1159
1253
|
[[ "$enable_time_tracking" == "true" ]] && features_list="${features_list}time-tracking,"
|
|
1160
1254
|
[[ "$enable_database" == "true" ]] && features_list="${features_list}database,"
|
|
1161
1255
|
[[ "$enable_beads" == "true" ]] && features_list="${features_list}beads,"
|
|
1256
|
+
[[ "$enable_sops" == "true" ]] && features_list="${features_list}sops,"
|
|
1162
1257
|
features_list="${features_list%,}" # Remove trailing comma
|
|
1163
1258
|
|
|
1164
1259
|
# Register repo in repos.json
|
|
@@ -1174,6 +1269,7 @@ EOF
|
|
|
1174
1269
|
[[ "$enable_time_tracking" == "true" ]] && echo " ✓ Time tracking (estimates, actuals)"
|
|
1175
1270
|
[[ "$enable_database" == "true" ]] && echo " ✓ Database (schemas/, migrations/, seeds/)"
|
|
1176
1271
|
[[ "$enable_beads" == "true" ]] && echo " ✓ Beads (task graph visualization)"
|
|
1272
|
+
[[ "$enable_sops" == "true" ]] && echo " ✓ SOPS (encrypted config files with age backend)"
|
|
1177
1273
|
echo ""
|
|
1178
1274
|
echo "Next steps:"
|
|
1179
1275
|
if [[ "$enable_beads" == "true" ]]; then
|
|
@@ -1558,12 +1654,27 @@ cmd_features() {
|
|
|
1558
1654
|
echo " - Ready task detection (/ready)"
|
|
1559
1655
|
echo " - Bi-directional sync with TODO.md/PLANS.md"
|
|
1560
1656
|
echo ""
|
|
1657
|
+
echo " sops Encrypted config files with SOPS + age"
|
|
1658
|
+
echo " - Value-level encryption (keys visible, values encrypted)"
|
|
1659
|
+
echo " - .sops.yaml with age backend (simpler than GPG)"
|
|
1660
|
+
echo " - Patterns: *.secret.yaml, configs/*.enc.json"
|
|
1661
|
+
echo " - See: .agents/tools/credentials/sops.md"
|
|
1662
|
+
echo ""
|
|
1663
|
+
echo "Extensibility:"
|
|
1664
|
+
echo ""
|
|
1665
|
+
echo " plugins Third-party agent plugins (configured in .aidevops.json)"
|
|
1666
|
+
echo " - Git repos deployed to ~/.aidevops/agents/<namespace>/"
|
|
1667
|
+
echo " - Namespaced to avoid collisions with core agents"
|
|
1668
|
+
echo " - Enable/disable per-plugin without removal"
|
|
1669
|
+
echo " - See: .agents/aidevops/plugins.md"
|
|
1670
|
+
echo ""
|
|
1561
1671
|
echo "Usage:"
|
|
1562
|
-
echo " aidevops init # Enable all features"
|
|
1672
|
+
echo " aidevops init # Enable all features (except sops)"
|
|
1563
1673
|
echo " aidevops init planning # Enable only planning"
|
|
1674
|
+
echo " aidevops init sops # Enable SOPS encryption"
|
|
1564
1675
|
echo " aidevops init beads # Enable beads (includes planning)"
|
|
1565
1676
|
echo " aidevops init database # Enable only database"
|
|
1566
|
-
echo " aidevops init planning,
|
|
1677
|
+
echo " aidevops init planning,sops # Enable multiple"
|
|
1567
1678
|
echo ""
|
|
1568
1679
|
}
|
|
1569
1680
|
|
|
@@ -1980,6 +2091,444 @@ cmd_skill() {
|
|
|
1980
2091
|
esac
|
|
1981
2092
|
}
|
|
1982
2093
|
|
|
2094
|
+
# Plugin management command
|
|
2095
|
+
cmd_plugin() {
|
|
2096
|
+
local action="${1:-help}"
|
|
2097
|
+
shift || true
|
|
2098
|
+
|
|
2099
|
+
local plugins_file="$CONFIG_DIR/plugins.json"
|
|
2100
|
+
local agents_dir="$AGENTS_DIR"
|
|
2101
|
+
|
|
2102
|
+
# Reserved namespaces that plugins cannot use
|
|
2103
|
+
local reserved_namespaces="custom draft scripts tools services workflows templates memory plugins seo wordpress aidevops"
|
|
2104
|
+
|
|
2105
|
+
# Ensure config dir exists
|
|
2106
|
+
mkdir -p "$CONFIG_DIR"
|
|
2107
|
+
|
|
2108
|
+
# Initialize plugins.json if missing
|
|
2109
|
+
if [[ ! -f "$plugins_file" ]]; then
|
|
2110
|
+
echo '{"plugins":[]}' > "$plugins_file"
|
|
2111
|
+
fi
|
|
2112
|
+
|
|
2113
|
+
#######################################
|
|
2114
|
+
# Validate a namespace is safe to use
|
|
2115
|
+
# Arguments: namespace
|
|
2116
|
+
# Returns: 0 if valid, 1 if reserved/invalid
|
|
2117
|
+
#######################################
|
|
2118
|
+
validate_namespace() {
|
|
2119
|
+
local ns="$1"
|
|
2120
|
+
# Must be lowercase alphanumeric with hyphens
|
|
2121
|
+
if [[ ! "$ns" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
|
2122
|
+
print_error "Invalid namespace '$ns': must be lowercase alphanumeric with hyphens, starting with a letter"
|
|
2123
|
+
return 1
|
|
2124
|
+
fi
|
|
2125
|
+
# Must not be reserved
|
|
2126
|
+
local reserved
|
|
2127
|
+
for reserved in $reserved_namespaces; do
|
|
2128
|
+
if [[ "$ns" == "$reserved" ]]; then
|
|
2129
|
+
print_error "Namespace '$ns' is reserved. Choose a different name."
|
|
2130
|
+
return 1
|
|
2131
|
+
fi
|
|
2132
|
+
done
|
|
2133
|
+
return 0
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
#######################################
|
|
2137
|
+
# Get a plugin field from plugins.json
|
|
2138
|
+
# Arguments: plugin_name, field
|
|
2139
|
+
#######################################
|
|
2140
|
+
get_plugin_field() {
|
|
2141
|
+
local name="$1"
|
|
2142
|
+
local field="$2"
|
|
2143
|
+
jq -r --arg n "$name" --arg f "$field" '.plugins[] | select(.name == $n) | .[$f] // empty' "$plugins_file" 2>/dev/null || echo ""
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
case "$action" in
|
|
2147
|
+
add|a)
|
|
2148
|
+
if [[ $# -lt 1 ]]; then
|
|
2149
|
+
print_error "Repository URL required"
|
|
2150
|
+
echo ""
|
|
2151
|
+
echo "Usage: aidevops plugin add <repo-url> [options]"
|
|
2152
|
+
echo ""
|
|
2153
|
+
echo "Options:"
|
|
2154
|
+
echo " --namespace <name> Namespace directory (default: derived from repo name)"
|
|
2155
|
+
echo " --branch <branch> Branch to track (default: main)"
|
|
2156
|
+
echo " --name <name> Human-readable name (default: derived from repo)"
|
|
2157
|
+
echo ""
|
|
2158
|
+
echo "Examples:"
|
|
2159
|
+
echo " aidevops plugin add https://github.com/marcusquinn/aidevops-pro.git --namespace pro"
|
|
2160
|
+
echo " aidevops plugin add https://github.com/marcusquinn/aidevops-anon.git --namespace anon"
|
|
2161
|
+
return 1
|
|
2162
|
+
fi
|
|
2163
|
+
|
|
2164
|
+
local repo_url="$1"
|
|
2165
|
+
shift
|
|
2166
|
+
local namespace="" branch="main" plugin_name=""
|
|
2167
|
+
|
|
2168
|
+
# Parse options
|
|
2169
|
+
while [[ $# -gt 0 ]]; do
|
|
2170
|
+
case "$1" in
|
|
2171
|
+
--namespace|--ns) namespace="$2"; shift 2 ;;
|
|
2172
|
+
--branch|-b) branch="$2"; shift 2 ;;
|
|
2173
|
+
--name|-n) plugin_name="$2"; shift 2 ;;
|
|
2174
|
+
*) print_error "Unknown option: $1"; return 1 ;;
|
|
2175
|
+
esac
|
|
2176
|
+
done
|
|
2177
|
+
|
|
2178
|
+
# Derive namespace from repo URL if not provided
|
|
2179
|
+
if [[ -z "$namespace" ]]; then
|
|
2180
|
+
namespace=$(basename "$repo_url" .git | sed 's/^aidevops-//')
|
|
2181
|
+
namespace=$(echo "$namespace" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')
|
|
2182
|
+
fi
|
|
2183
|
+
|
|
2184
|
+
# Derive name from namespace if not provided
|
|
2185
|
+
if [[ -z "$plugin_name" ]]; then
|
|
2186
|
+
plugin_name="$namespace"
|
|
2187
|
+
fi
|
|
2188
|
+
|
|
2189
|
+
# Validate namespace
|
|
2190
|
+
if ! validate_namespace "$namespace"; then
|
|
2191
|
+
return 1
|
|
2192
|
+
fi
|
|
2193
|
+
|
|
2194
|
+
# Check if plugin already exists
|
|
2195
|
+
local existing
|
|
2196
|
+
existing=$(jq -r --arg n "$plugin_name" '.plugins[] | select(.name == $n) | .name' "$plugins_file" 2>/dev/null || echo "")
|
|
2197
|
+
if [[ -n "$existing" ]]; then
|
|
2198
|
+
print_error "Plugin '$plugin_name' already exists. Use 'aidevops plugin update $plugin_name' to update."
|
|
2199
|
+
return 1
|
|
2200
|
+
fi
|
|
2201
|
+
|
|
2202
|
+
# Check if namespace is already in use
|
|
2203
|
+
if [[ -d "$agents_dir/$namespace" ]]; then
|
|
2204
|
+
local ns_owner
|
|
2205
|
+
ns_owner=$(jq -r --arg ns "$namespace" '.plugins[] | select(.namespace == $ns) | .name' "$plugins_file" 2>/dev/null || echo "")
|
|
2206
|
+
if [[ -n "$ns_owner" ]]; then
|
|
2207
|
+
print_error "Namespace '$namespace' is already used by plugin '$ns_owner'"
|
|
2208
|
+
else
|
|
2209
|
+
print_error "Directory '$agents_dir/$namespace/' already exists"
|
|
2210
|
+
echo " Choose a different namespace with --namespace <name>"
|
|
2211
|
+
fi
|
|
2212
|
+
return 1
|
|
2213
|
+
fi
|
|
2214
|
+
|
|
2215
|
+
print_info "Adding plugin '$plugin_name' from $repo_url..."
|
|
2216
|
+
print_info " Namespace: $namespace"
|
|
2217
|
+
print_info " Branch: $branch"
|
|
2218
|
+
|
|
2219
|
+
# Clone the repo
|
|
2220
|
+
local clone_dir="$agents_dir/$namespace"
|
|
2221
|
+
if ! git clone --branch "$branch" --depth 1 "$repo_url" "$clone_dir" 2>&1; then
|
|
2222
|
+
print_error "Failed to clone repository"
|
|
2223
|
+
rm -rf "$clone_dir" 2>/dev/null || true
|
|
2224
|
+
return 1
|
|
2225
|
+
fi
|
|
2226
|
+
|
|
2227
|
+
# Remove .git directory (we track via plugins.json, not nested git)
|
|
2228
|
+
rm -rf "$clone_dir/.git"
|
|
2229
|
+
|
|
2230
|
+
# Add to plugins.json
|
|
2231
|
+
local tmp_file="${plugins_file}.tmp"
|
|
2232
|
+
jq --arg name "$plugin_name" \
|
|
2233
|
+
--arg repo "$repo_url" \
|
|
2234
|
+
--arg branch "$branch" \
|
|
2235
|
+
--arg ns "$namespace" \
|
|
2236
|
+
'.plugins += [{"name": $name, "repo": $repo, "branch": $branch, "namespace": $ns, "enabled": true}]' \
|
|
2237
|
+
"$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file"
|
|
2238
|
+
|
|
2239
|
+
print_success "Plugin '$plugin_name' installed to $clone_dir"
|
|
2240
|
+
echo ""
|
|
2241
|
+
echo " Agents available at: ~/.aidevops/agents/$namespace/"
|
|
2242
|
+
echo " Update: aidevops plugin update $plugin_name"
|
|
2243
|
+
echo " Remove: aidevops plugin remove $plugin_name"
|
|
2244
|
+
;;
|
|
2245
|
+
|
|
2246
|
+
list|ls|l)
|
|
2247
|
+
local count
|
|
2248
|
+
count=$(jq '.plugins | length' "$plugins_file" 2>/dev/null || echo "0")
|
|
2249
|
+
|
|
2250
|
+
if [[ "$count" == "0" ]]; then
|
|
2251
|
+
echo "No plugins installed."
|
|
2252
|
+
echo ""
|
|
2253
|
+
echo "Add a plugin: aidevops plugin add <repo-url> --namespace <name>"
|
|
2254
|
+
return 0
|
|
2255
|
+
fi
|
|
2256
|
+
|
|
2257
|
+
echo "Installed plugins ($count):"
|
|
2258
|
+
echo ""
|
|
2259
|
+
printf " %-15s %-10s %-8s %s\n" "NAME" "NAMESPACE" "ENABLED" "REPO"
|
|
2260
|
+
printf " %-15s %-10s %-8s %s\n" "----" "---------" "-------" "----"
|
|
2261
|
+
|
|
2262
|
+
jq -r '.plugins[] | " \(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"' "$plugins_file" 2>/dev/null | \
|
|
2263
|
+
while IFS=$'\t' read -r name ns enabled repo; do
|
|
2264
|
+
local status_icon="yes"
|
|
2265
|
+
if [[ "$enabled" == "false" ]]; then
|
|
2266
|
+
status_icon="no"
|
|
2267
|
+
fi
|
|
2268
|
+
printf " %-15s %-10s %-8s %s\n" "$name" "$ns" "$status_icon" "$repo"
|
|
2269
|
+
done
|
|
2270
|
+
;;
|
|
2271
|
+
|
|
2272
|
+
update|u)
|
|
2273
|
+
local target="${1:-}"
|
|
2274
|
+
|
|
2275
|
+
if [[ -n "$target" ]]; then
|
|
2276
|
+
# Update specific plugin
|
|
2277
|
+
local repo ns branch_name
|
|
2278
|
+
repo=$(get_plugin_field "$target" "repo")
|
|
2279
|
+
ns=$(get_plugin_field "$target" "namespace")
|
|
2280
|
+
branch_name=$(get_plugin_field "$target" "branch")
|
|
2281
|
+
branch_name="${branch_name:-main}"
|
|
2282
|
+
|
|
2283
|
+
if [[ -z "$repo" ]]; then
|
|
2284
|
+
print_error "Plugin '$target' not found"
|
|
2285
|
+
return 1
|
|
2286
|
+
fi
|
|
2287
|
+
|
|
2288
|
+
print_info "Updating plugin '$target'..."
|
|
2289
|
+
local clone_dir="$agents_dir/$ns"
|
|
2290
|
+
rm -rf "$clone_dir"
|
|
2291
|
+
if git clone --branch "$branch_name" --depth 1 "$repo" "$clone_dir" 2>&1; then
|
|
2292
|
+
rm -rf "$clone_dir/.git"
|
|
2293
|
+
print_success "Plugin '$target' updated"
|
|
2294
|
+
else
|
|
2295
|
+
print_error "Failed to update plugin '$target'"
|
|
2296
|
+
return 1
|
|
2297
|
+
fi
|
|
2298
|
+
else
|
|
2299
|
+
# Update all enabled plugins
|
|
2300
|
+
local names
|
|
2301
|
+
names=$(jq -r '.plugins[] | select(.enabled != false) | .name' "$plugins_file" 2>/dev/null || echo "")
|
|
2302
|
+
if [[ -z "$names" ]]; then
|
|
2303
|
+
echo "No enabled plugins to update."
|
|
2304
|
+
return 0
|
|
2305
|
+
fi
|
|
2306
|
+
|
|
2307
|
+
local failed=0
|
|
2308
|
+
while IFS= read -r pname; do
|
|
2309
|
+
[[ -z "$pname" ]] && continue
|
|
2310
|
+
local prepo pns pbranch
|
|
2311
|
+
prepo=$(get_plugin_field "$pname" "repo")
|
|
2312
|
+
pns=$(get_plugin_field "$pname" "namespace")
|
|
2313
|
+
pbranch=$(get_plugin_field "$pname" "branch")
|
|
2314
|
+
pbranch="${pbranch:-main}"
|
|
2315
|
+
|
|
2316
|
+
print_info "Updating '$pname'..."
|
|
2317
|
+
local pdir="$agents_dir/$pns"
|
|
2318
|
+
rm -rf "$pdir"
|
|
2319
|
+
if git clone --branch "$pbranch" --depth 1 "$prepo" "$pdir" 2>/dev/null; then
|
|
2320
|
+
rm -rf "$pdir/.git"
|
|
2321
|
+
print_success " '$pname' updated"
|
|
2322
|
+
else
|
|
2323
|
+
print_error " '$pname' failed to update"
|
|
2324
|
+
failed=$((failed + 1))
|
|
2325
|
+
fi
|
|
2326
|
+
done <<< "$names"
|
|
2327
|
+
|
|
2328
|
+
if [[ "$failed" -gt 0 ]]; then
|
|
2329
|
+
print_warning "$failed plugin(s) failed to update"
|
|
2330
|
+
return 1
|
|
2331
|
+
fi
|
|
2332
|
+
print_success "All plugins updated"
|
|
2333
|
+
fi
|
|
2334
|
+
;;
|
|
2335
|
+
|
|
2336
|
+
enable)
|
|
2337
|
+
if [[ $# -lt 1 ]]; then
|
|
2338
|
+
print_error "Plugin name required"
|
|
2339
|
+
echo "Usage: aidevops plugin enable <name>"
|
|
2340
|
+
return 1
|
|
2341
|
+
fi
|
|
2342
|
+
local target_name="$1"
|
|
2343
|
+
local target_repo target_ns target_branch
|
|
2344
|
+
target_repo=$(get_plugin_field "$target_name" "repo")
|
|
2345
|
+
if [[ -z "$target_repo" ]]; then
|
|
2346
|
+
print_error "Plugin '$target_name' not found"
|
|
2347
|
+
return 1
|
|
2348
|
+
fi
|
|
2349
|
+
|
|
2350
|
+
target_ns=$(get_plugin_field "$target_name" "namespace")
|
|
2351
|
+
target_branch=$(get_plugin_field "$target_name" "branch")
|
|
2352
|
+
target_branch="${target_branch:-main}"
|
|
2353
|
+
|
|
2354
|
+
# Update enabled flag
|
|
2355
|
+
local tmp_file="${plugins_file}.tmp"
|
|
2356
|
+
jq --arg n "$target_name" '(.plugins[] | select(.name == $n)).enabled = true' "$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file"
|
|
2357
|
+
|
|
2358
|
+
# Deploy if not already present
|
|
2359
|
+
if [[ ! -d "$agents_dir/$target_ns" ]]; then
|
|
2360
|
+
print_info "Deploying plugin '$target_name'..."
|
|
2361
|
+
if git clone --branch "$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns" 2>/dev/null; then
|
|
2362
|
+
rm -rf "$agents_dir/$target_ns/.git"
|
|
2363
|
+
fi
|
|
2364
|
+
fi
|
|
2365
|
+
|
|
2366
|
+
print_success "Plugin '$target_name' enabled"
|
|
2367
|
+
;;
|
|
2368
|
+
|
|
2369
|
+
disable)
|
|
2370
|
+
if [[ $# -lt 1 ]]; then
|
|
2371
|
+
print_error "Plugin name required"
|
|
2372
|
+
echo "Usage: aidevops plugin disable <name>"
|
|
2373
|
+
return 1
|
|
2374
|
+
fi
|
|
2375
|
+
local target_name="$1"
|
|
2376
|
+
local target_ns
|
|
2377
|
+
target_ns=$(get_plugin_field "$target_name" "namespace")
|
|
2378
|
+
if [[ -z "$target_ns" ]]; then
|
|
2379
|
+
print_error "Plugin '$target_name' not found"
|
|
2380
|
+
return 1
|
|
2381
|
+
fi
|
|
2382
|
+
|
|
2383
|
+
# Update enabled flag
|
|
2384
|
+
local tmp_file="${plugins_file}.tmp"
|
|
2385
|
+
jq --arg n "$target_name" '(.plugins[] | select(.name == $n)).enabled = false' "$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file"
|
|
2386
|
+
|
|
2387
|
+
# Remove deployed files
|
|
2388
|
+
if [[ -d "$agents_dir/$target_ns" ]]; then
|
|
2389
|
+
rm -rf "$agents_dir/$target_ns"
|
|
2390
|
+
fi
|
|
2391
|
+
|
|
2392
|
+
print_success "Plugin '$target_name' disabled (config preserved)"
|
|
2393
|
+
;;
|
|
2394
|
+
|
|
2395
|
+
remove|rm)
|
|
2396
|
+
if [[ $# -lt 1 ]]; then
|
|
2397
|
+
print_error "Plugin name required"
|
|
2398
|
+
echo "Usage: aidevops plugin remove <name>"
|
|
2399
|
+
return 1
|
|
2400
|
+
fi
|
|
2401
|
+
local target_name="$1"
|
|
2402
|
+
local target_ns
|
|
2403
|
+
target_ns=$(get_plugin_field "$target_name" "namespace")
|
|
2404
|
+
if [[ -z "$target_ns" ]]; then
|
|
2405
|
+
print_error "Plugin '$target_name' not found"
|
|
2406
|
+
return 1
|
|
2407
|
+
fi
|
|
2408
|
+
|
|
2409
|
+
# Remove deployed files
|
|
2410
|
+
if [[ -d "$agents_dir/$target_ns" ]]; then
|
|
2411
|
+
rm -rf "$agents_dir/$target_ns"
|
|
2412
|
+
print_info "Removed $agents_dir/$target_ns/"
|
|
2413
|
+
fi
|
|
2414
|
+
|
|
2415
|
+
# Remove from plugins.json
|
|
2416
|
+
local tmp_file="${plugins_file}.tmp"
|
|
2417
|
+
jq --arg n "$target_name" '.plugins = [.plugins[] | select(.name != $n)]' "$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file"
|
|
2418
|
+
|
|
2419
|
+
print_success "Plugin '$target_name' removed"
|
|
2420
|
+
;;
|
|
2421
|
+
|
|
2422
|
+
init)
|
|
2423
|
+
local target_dir="${1:-.}"
|
|
2424
|
+
local plugin_name="${2:-my-plugin}"
|
|
2425
|
+
local namespace="${3:-$plugin_name}"
|
|
2426
|
+
|
|
2427
|
+
if [[ "$target_dir" != "." && -d "$target_dir" ]]; then
|
|
2428
|
+
local existing_count
|
|
2429
|
+
existing_count=$(find "$target_dir" -maxdepth 1 -type f | wc -l | tr -d ' ')
|
|
2430
|
+
if [[ "$existing_count" -gt 0 ]]; then
|
|
2431
|
+
print_error "Directory '$target_dir' already has files. Use an empty directory."
|
|
2432
|
+
return 1
|
|
2433
|
+
fi
|
|
2434
|
+
fi
|
|
2435
|
+
|
|
2436
|
+
mkdir -p "$target_dir"
|
|
2437
|
+
|
|
2438
|
+
local template_dir="$agents_dir/templates/plugin-template"
|
|
2439
|
+
if [[ ! -d "$template_dir" ]]; then
|
|
2440
|
+
print_error "Plugin template not found at $template_dir"
|
|
2441
|
+
print_info "Run 'aidevops update' to get the latest templates."
|
|
2442
|
+
return 1
|
|
2443
|
+
fi
|
|
2444
|
+
|
|
2445
|
+
# Copy template files with placeholder substitution
|
|
2446
|
+
local plugin_name_upper
|
|
2447
|
+
plugin_name_upper=$(echo "$plugin_name" | tr '[:lower:]' '[:upper:]' | tr '-' '_')
|
|
2448
|
+
|
|
2449
|
+
# AGENTS.md
|
|
2450
|
+
sed -e "s|{{PLUGIN_NAME}}|$plugin_name|g" \
|
|
2451
|
+
-e "s|{{PLUGIN_NAME_UPPER}}|$plugin_name_upper|g" \
|
|
2452
|
+
-e "s|{{NAMESPACE}}|$namespace|g" \
|
|
2453
|
+
-e "s|{{REPO_URL}}|https://github.com/user/aidevops-$namespace.git|g" \
|
|
2454
|
+
"$template_dir/AGENTS.md" > "$target_dir/AGENTS.md"
|
|
2455
|
+
|
|
2456
|
+
# Main agent file
|
|
2457
|
+
sed -e "s|{{PLUGIN_NAME}}|$plugin_name|g" \
|
|
2458
|
+
-e "s|{{PLUGIN_DESCRIPTION}}|$plugin_name plugin for aidevops|g" \
|
|
2459
|
+
-e "s|{{NAMESPACE}}|$namespace|g" \
|
|
2460
|
+
"$template_dir/main-agent.md" > "$target_dir/$namespace.md"
|
|
2461
|
+
|
|
2462
|
+
# Example subagent directory
|
|
2463
|
+
mkdir -p "$target_dir/$namespace"
|
|
2464
|
+
sed -e "s|{{PLUGIN_NAME}}|$plugin_name|g" \
|
|
2465
|
+
-e "s|{{NAMESPACE}}|$namespace|g" \
|
|
2466
|
+
"$template_dir/example-subagent.md" > "$target_dir/$namespace/example.md"
|
|
2467
|
+
|
|
2468
|
+
# Scripts directory
|
|
2469
|
+
mkdir -p "$target_dir/scripts"
|
|
2470
|
+
|
|
2471
|
+
print_success "Plugin scaffolded in $target_dir/"
|
|
2472
|
+
echo ""
|
|
2473
|
+
echo "Structure:"
|
|
2474
|
+
echo " $target_dir/"
|
|
2475
|
+
echo " ├── AGENTS.md # Plugin documentation"
|
|
2476
|
+
echo " ├── $namespace.md # Main agent"
|
|
2477
|
+
echo " ├── $namespace/"
|
|
2478
|
+
echo " │ └── example.md # Example subagent"
|
|
2479
|
+
echo " └── scripts/ # Helper scripts (empty)"
|
|
2480
|
+
echo ""
|
|
2481
|
+
echo "Next steps:"
|
|
2482
|
+
echo " 1. Edit $namespace.md with your agent instructions"
|
|
2483
|
+
echo " 2. Add subagents to $namespace/"
|
|
2484
|
+
echo " 3. Push to a git repo"
|
|
2485
|
+
echo " 4. Install: aidevops plugin add <repo-url> --namespace $namespace"
|
|
2486
|
+
;;
|
|
2487
|
+
|
|
2488
|
+
help|--help|-h)
|
|
2489
|
+
print_header "Plugin Management"
|
|
2490
|
+
echo ""
|
|
2491
|
+
echo "Manage third-party agent plugins that extend aidevops."
|
|
2492
|
+
echo "Plugins deploy to ~/.aidevops/agents/<namespace>/ (isolated from core)."
|
|
2493
|
+
echo ""
|
|
2494
|
+
echo "Usage: aidevops plugin <command> [options]"
|
|
2495
|
+
echo ""
|
|
2496
|
+
echo "Commands:"
|
|
2497
|
+
echo " add <repo-url> Install a plugin from a git repository"
|
|
2498
|
+
echo " list List installed plugins"
|
|
2499
|
+
echo " update [name] Update specific or all plugins"
|
|
2500
|
+
echo " enable <name> Enable a disabled plugin (redeploys files)"
|
|
2501
|
+
echo " disable <name> Disable a plugin (removes files, keeps config)"
|
|
2502
|
+
echo " remove <name> Remove a plugin entirely"
|
|
2503
|
+
echo " init [dir] [name] Scaffold a new plugin from template"
|
|
2504
|
+
echo ""
|
|
2505
|
+
echo "Options for 'add':"
|
|
2506
|
+
echo " --namespace <name> Directory name under ~/.aidevops/agents/"
|
|
2507
|
+
echo " --branch <branch> Branch to track (default: main)"
|
|
2508
|
+
echo " --name <name> Human-readable plugin name"
|
|
2509
|
+
echo ""
|
|
2510
|
+
echo "Examples:"
|
|
2511
|
+
echo " aidevops plugin add https://github.com/marcusquinn/aidevops-pro.git --namespace pro"
|
|
2512
|
+
echo " aidevops plugin add https://github.com/marcusquinn/aidevops-anon.git --namespace anon"
|
|
2513
|
+
echo " aidevops plugin list"
|
|
2514
|
+
echo " aidevops plugin update"
|
|
2515
|
+
echo " aidevops plugin update pro"
|
|
2516
|
+
echo " aidevops plugin disable pro"
|
|
2517
|
+
echo " aidevops plugin enable pro"
|
|
2518
|
+
echo " aidevops plugin remove pro"
|
|
2519
|
+
echo " aidevops plugin init ./my-plugin my-plugin"
|
|
2520
|
+
echo ""
|
|
2521
|
+
echo "Plugin docs: ~/.aidevops/agents/aidevops/plugins.md"
|
|
2522
|
+
;;
|
|
2523
|
+
*)
|
|
2524
|
+
print_error "Unknown plugin command: $action"
|
|
2525
|
+
echo "Run 'aidevops plugin help' for usage information."
|
|
2526
|
+
return 1
|
|
2527
|
+
;;
|
|
2528
|
+
esac
|
|
2529
|
+
return 0
|
|
2530
|
+
}
|
|
2531
|
+
|
|
1983
2532
|
# Help command
|
|
1984
2533
|
cmd_help() {
|
|
1985
2534
|
local version
|
|
@@ -1994,6 +2543,7 @@ cmd_help() {
|
|
|
1994
2543
|
echo " upgrade-planning Upgrade TODO.md/PLANS.md to latest templates"
|
|
1995
2544
|
echo " features List available features for init"
|
|
1996
2545
|
echo " skill <cmd> Manage agent skills (add/list/check/update/remove)"
|
|
2546
|
+
echo " plugin <cmd> Manage plugins (add/list/update/enable/disable/remove)"
|
|
1997
2547
|
echo " status Check installation status of all components"
|
|
1998
2548
|
echo " update Update aidevops to the latest version (alias: upgrade)"
|
|
1999
2549
|
echo " upgrade Alias for update"
|
|
@@ -2027,6 +2577,12 @@ cmd_help() {
|
|
|
2027
2577
|
echo " aidevops secret import # Import from credentials.sh to gopass"
|
|
2028
2578
|
echo " aidevops secret status # Show backend status"
|
|
2029
2579
|
echo ""
|
|
2580
|
+
echo "Plugins:"
|
|
2581
|
+
echo " aidevops plugin add <url> # Install a plugin from git repo"
|
|
2582
|
+
echo " aidevops plugin list # List installed plugins"
|
|
2583
|
+
echo " aidevops plugin update # Update all plugins"
|
|
2584
|
+
echo " aidevops plugin remove <n> # Remove a plugin"
|
|
2585
|
+
echo ""
|
|
2030
2586
|
echo "Skills:"
|
|
2031
2587
|
echo " aidevops skill add <source> # Import a skill from GitHub"
|
|
2032
2588
|
echo " aidevops skill list # List imported skills"
|
|
@@ -2118,6 +2674,10 @@ main() {
|
|
|
2118
2674
|
shift
|
|
2119
2675
|
cmd_skill "$@"
|
|
2120
2676
|
;;
|
|
2677
|
+
plugin|plugins)
|
|
2678
|
+
shift
|
|
2679
|
+
cmd_plugin "$@"
|
|
2680
|
+
;;
|
|
2121
2681
|
detect|scan)
|
|
2122
2682
|
cmd_detect
|
|
2123
2683
|
;;
|
package/package.json
CHANGED
package/setup.sh
CHANGED
|
@@ -4,7 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
# AI Assistant Server Access Framework Setup Script
|
|
5
5
|
# Helps developers set up the framework for their infrastructure
|
|
6
6
|
#
|
|
7
|
-
# Version: 2.
|
|
7
|
+
# Version: 2.107.0
|
|
8
8
|
#
|
|
9
9
|
# Quick Install:
|
|
10
10
|
# npm install -g aidevops && aidevops update (recommended)
|
|
@@ -21,7 +21,7 @@ NC='\033[0m' # No Color
|
|
|
21
21
|
# Global flags
|
|
22
22
|
CLEAN_MODE=false
|
|
23
23
|
INTERACTIVE_MODE=false
|
|
24
|
-
NON_INTERACTIVE=false
|
|
24
|
+
NON_INTERACTIVE="${AIDEVOPS_NON_INTERACTIVE:-false}"
|
|
25
25
|
UPDATE_TOOLS_MODE=false
|
|
26
26
|
REPO_URL="https://github.com/marcusquinn/aidevops.git"
|
|
27
27
|
INSTALL_DIR="$HOME/Git/aidevops"
|
|
@@ -2542,6 +2542,7 @@ deploy_aidevops_agents() {
|
|
|
2542
2542
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
2543
2543
|
local source_dir="$script_dir/.agents"
|
|
2544
2544
|
local target_dir="$HOME/.aidevops/agents"
|
|
2545
|
+
local plugins_file="$HOME/.config/aidevops/plugins.json"
|
|
2545
2546
|
|
|
2546
2547
|
# Validate source directory exists (catches curl install from wrong directory)
|
|
2547
2548
|
if [[ ! -d "$source_dir" ]]; then
|
|
@@ -2554,6 +2555,14 @@ deploy_aidevops_agents() {
|
|
|
2554
2555
|
return 1
|
|
2555
2556
|
fi
|
|
2556
2557
|
|
|
2558
|
+
# Collect plugin namespace directories to preserve during deployment
|
|
2559
|
+
local -a plugin_namespaces=()
|
|
2560
|
+
if [[ -f "$plugins_file" ]] && command -v jq &>/dev/null; then
|
|
2561
|
+
while IFS= read -r ns; do
|
|
2562
|
+
[[ -n "$ns" ]] && plugin_namespaces+=("$ns")
|
|
2563
|
+
done < <(jq -r '.plugins[].namespace // empty' "$plugins_file" 2>/dev/null)
|
|
2564
|
+
fi
|
|
2565
|
+
|
|
2557
2566
|
# Create backup if target exists (with rotation)
|
|
2558
2567
|
if [[ -d "$target_dir" ]]; then
|
|
2559
2568
|
create_backup_with_rotation "$target_dir" "agents"
|
|
@@ -2562,14 +2571,20 @@ deploy_aidevops_agents() {
|
|
|
2562
2571
|
# Create target directory and copy agents
|
|
2563
2572
|
mkdir -p "$target_dir"
|
|
2564
2573
|
|
|
2565
|
-
# If clean mode, remove stale files first (preserving user directories)
|
|
2574
|
+
# If clean mode, remove stale files first (preserving user and plugin directories)
|
|
2566
2575
|
if [[ "$CLEAN_MODE" == "true" ]]; then
|
|
2567
|
-
|
|
2568
|
-
# Preserve user-managed directories before clean
|
|
2576
|
+
# Build list of directories to preserve: custom, draft, plus plugin namespaces
|
|
2569
2577
|
local -a preserved_dirs=("custom" "draft")
|
|
2570
|
-
|
|
2578
|
+
if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then
|
|
2579
|
+
for pns in "${plugin_namespaces[@]}"; do
|
|
2580
|
+
preserved_dirs+=("$pns")
|
|
2581
|
+
done
|
|
2582
|
+
fi
|
|
2583
|
+
print_info "Clean mode: removing stale files from $target_dir (preserving ${preserved_dirs[*]})"
|
|
2584
|
+
local tmp_preserve
|
|
2585
|
+
tmp_preserve="$(mktemp -d)"
|
|
2571
2586
|
if [[ -z "$tmp_preserve" || ! -d "$tmp_preserve" ]]; then
|
|
2572
|
-
print_error "Failed to create temp dir for preserving
|
|
2587
|
+
print_error "Failed to create temp dir for preserving agents"
|
|
2573
2588
|
return 1
|
|
2574
2589
|
fi
|
|
2575
2590
|
local preserve_failed=false
|
|
@@ -2581,7 +2596,7 @@ deploy_aidevops_agents() {
|
|
|
2581
2596
|
fi
|
|
2582
2597
|
done
|
|
2583
2598
|
if [[ "$preserve_failed" == "true" ]]; then
|
|
2584
|
-
print_error "Failed to preserve
|
|
2599
|
+
print_error "Failed to preserve user/plugin agents; aborting clean"
|
|
2585
2600
|
rm -rf "$tmp_preserve"
|
|
2586
2601
|
return 1
|
|
2587
2602
|
fi
|
|
@@ -2599,15 +2614,28 @@ deploy_aidevops_agents() {
|
|
|
2599
2614
|
# - loop-state/ (local runtime state, not agents)
|
|
2600
2615
|
# - custom/ (user's private agents, never overwritten)
|
|
2601
2616
|
# - draft/ (user's experimental agents, never overwritten)
|
|
2617
|
+
# - plugin namespace directories (managed separately)
|
|
2602
2618
|
# Use rsync for selective exclusion
|
|
2603
2619
|
local deploy_ok=false
|
|
2604
2620
|
if command -v rsync &>/dev/null; then
|
|
2605
|
-
|
|
2621
|
+
local -a rsync_excludes=("--exclude=loop-state/" "--exclude=custom/" "--exclude=draft/")
|
|
2622
|
+
if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then
|
|
2623
|
+
for pns in "${plugin_namespaces[@]}"; do
|
|
2624
|
+
rsync_excludes+=("--exclude=${pns}/")
|
|
2625
|
+
done
|
|
2626
|
+
fi
|
|
2627
|
+
if rsync -a "${rsync_excludes[@]}" "$source_dir/" "$target_dir/"; then
|
|
2606
2628
|
deploy_ok=true
|
|
2607
2629
|
fi
|
|
2608
2630
|
else
|
|
2609
2631
|
# Fallback: use tar with exclusions to match rsync behavior
|
|
2610
|
-
|
|
2632
|
+
local -a tar_excludes=("--exclude=loop-state" "--exclude=custom" "--exclude=draft")
|
|
2633
|
+
if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then
|
|
2634
|
+
for pns in "${plugin_namespaces[@]}"; do
|
|
2635
|
+
tar_excludes+=("--exclude=$pns")
|
|
2636
|
+
done
|
|
2637
|
+
fi
|
|
2638
|
+
if (cd "$source_dir" && tar cf - "${tar_excludes[@]}" .) | (cd "$target_dir" && tar xf -); then
|
|
2611
2639
|
deploy_ok=true
|
|
2612
2640
|
fi
|
|
2613
2641
|
fi
|
|
@@ -2675,6 +2703,9 @@ deploy_aidevops_agents() {
|
|
|
2675
2703
|
print_warning "Mailbox migration had issues (non-critical, old files preserved)"
|
|
2676
2704
|
fi
|
|
2677
2705
|
fi
|
|
2706
|
+
|
|
2707
|
+
# Deploy enabled plugins from plugins.json
|
|
2708
|
+
deploy_plugins "$target_dir" "$plugins_file"
|
|
2678
2709
|
else
|
|
2679
2710
|
print_error "Failed to deploy agents"
|
|
2680
2711
|
return 1
|
|
@@ -2683,6 +2714,93 @@ deploy_aidevops_agents() {
|
|
|
2683
2714
|
return 0
|
|
2684
2715
|
}
|
|
2685
2716
|
|
|
2717
|
+
# Deploy enabled plugins from plugins.json
|
|
2718
|
+
# Arguments: target_dir, plugins_file
|
|
2719
|
+
deploy_plugins() {
|
|
2720
|
+
local target_dir="$1"
|
|
2721
|
+
local plugins_file="$2"
|
|
2722
|
+
|
|
2723
|
+
# Skip if no plugins.json or no jq
|
|
2724
|
+
if [[ ! -f "$plugins_file" ]]; then
|
|
2725
|
+
return 0
|
|
2726
|
+
fi
|
|
2727
|
+
if ! command -v jq &>/dev/null; then
|
|
2728
|
+
print_warning "jq not found; skipping plugin deployment"
|
|
2729
|
+
return 0
|
|
2730
|
+
fi
|
|
2731
|
+
|
|
2732
|
+
local plugin_count
|
|
2733
|
+
plugin_count=$(jq '.plugins | length' "$plugins_file" 2>/dev/null || echo "0")
|
|
2734
|
+
if [[ "$plugin_count" -eq 0 ]]; then
|
|
2735
|
+
return 0
|
|
2736
|
+
fi
|
|
2737
|
+
|
|
2738
|
+
local enabled_count
|
|
2739
|
+
enabled_count=$(jq '[.plugins[] | select(.enabled != false)] | length' "$plugins_file" 2>/dev/null || echo "0")
|
|
2740
|
+
if [[ "$enabled_count" -eq 0 ]]; then
|
|
2741
|
+
print_info "No enabled plugins to deploy ($plugin_count configured, all disabled)"
|
|
2742
|
+
return 0
|
|
2743
|
+
fi
|
|
2744
|
+
|
|
2745
|
+
# Remove directories for disabled plugins (cleanup)
|
|
2746
|
+
local disabled_ns
|
|
2747
|
+
while IFS= read -r disabled_ns; do
|
|
2748
|
+
[[ -z "$disabled_ns" ]] && continue
|
|
2749
|
+
if [[ -d "$target_dir/$disabled_ns" ]]; then
|
|
2750
|
+
rm -rf "$target_dir/$disabled_ns"
|
|
2751
|
+
print_info " Removed disabled plugin directory: $disabled_ns"
|
|
2752
|
+
fi
|
|
2753
|
+
done < <(jq -r '.plugins[] | select(.enabled == false) | .namespace // empty' "$plugins_file" 2>/dev/null)
|
|
2754
|
+
|
|
2755
|
+
print_info "Deploying $enabled_count plugin(s)..."
|
|
2756
|
+
|
|
2757
|
+
local deployed=0
|
|
2758
|
+
local failed=0
|
|
2759
|
+
local skipped=0
|
|
2760
|
+
|
|
2761
|
+
# Process each enabled plugin
|
|
2762
|
+
while IFS=$'\t' read -r pname prepo pns pbranch; do
|
|
2763
|
+
[[ -z "$pname" ]] && continue
|
|
2764
|
+
pbranch="${pbranch:-main}"
|
|
2765
|
+
local clone_dir="$target_dir/$pns"
|
|
2766
|
+
|
|
2767
|
+
if [[ -d "$clone_dir" ]]; then
|
|
2768
|
+
# Plugin directory exists — skip re-clone during setup
|
|
2769
|
+
# Users can force update via: aidevops plugin update [name]
|
|
2770
|
+
skipped=$((skipped + 1))
|
|
2771
|
+
continue
|
|
2772
|
+
fi
|
|
2773
|
+
|
|
2774
|
+
# Clone plugin repo
|
|
2775
|
+
print_info " Installing plugin '$pname' ($prepo)..."
|
|
2776
|
+
if git clone --branch "$pbranch" --depth 1 "$prepo" "$clone_dir" 2>/dev/null; then
|
|
2777
|
+
# Remove .git directory (tracked via plugins.json, not nested git)
|
|
2778
|
+
rm -rf "$clone_dir/.git"
|
|
2779
|
+
# Set permissions on any scripts
|
|
2780
|
+
if [[ -d "$clone_dir/scripts" ]]; then
|
|
2781
|
+
chmod +x "$clone_dir/scripts/"*.sh 2>/dev/null || true
|
|
2782
|
+
fi
|
|
2783
|
+
deployed=$((deployed + 1))
|
|
2784
|
+
else
|
|
2785
|
+
print_warning " Failed to install plugin '$pname' (network or auth issue)"
|
|
2786
|
+
failed=$((failed + 1))
|
|
2787
|
+
fi
|
|
2788
|
+
done < <(jq -r '.plugins[] | select(.enabled != false) | [.name, .repo, .namespace, (.branch // "main")] | @tsv' "$plugins_file" 2>/dev/null)
|
|
2789
|
+
|
|
2790
|
+
# Summary
|
|
2791
|
+
if [[ "$deployed" -gt 0 ]]; then
|
|
2792
|
+
print_success "Deployed $deployed plugin(s)"
|
|
2793
|
+
fi
|
|
2794
|
+
if [[ "$skipped" -gt 0 ]]; then
|
|
2795
|
+
print_info "$skipped plugin(s) already deployed (use 'aidevops plugin update' to refresh)"
|
|
2796
|
+
fi
|
|
2797
|
+
if [[ "$failed" -gt 0 ]]; then
|
|
2798
|
+
print_warning "$failed plugin(s) failed to deploy (non-blocking)"
|
|
2799
|
+
fi
|
|
2800
|
+
|
|
2801
|
+
return 0
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2686
2804
|
# Generate Agent Skills SKILL.md files for cross-tool compatibility
|
|
2687
2805
|
generate_agent_skills() {
|
|
2688
2806
|
print_info "Generating Agent Skills SKILL.md files..."
|