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 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
- - Primary agents (Build+, SEO, Marketing, etc.) with @plan-plus subagent for planning-only mode
96
- - 765+ subagent markdown files organized by domain
97
- - 172 helper scripts in `.agents/scripts/`
98
- - 28 slash commands for common workflows
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/`, 172 helper scripts |
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: 15 primary agents, 765+ subagents, and 172 helper scripts.
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
- Ordered as they appear in OpenCode Tab selector and other AI assistants (15 total):
1476
-
1477
- | Name | File | Purpose | MCPs Enabled |
1478
- |------|------|---------|--------------|
1479
- | @plan-plus | `plan-plus.md` | Planning-only subagent (Build+ handles planning by default) | context7, augment, repomix |
1480
- | Build+ | `build-plus.md` | Enhanced Build with context tools | context7, augment, repomix |
1481
- | Build-Agent | `build-agent.md` | Design and improve AI agents | context7, augment, repomix |
1482
- | Build-MCP | `build-mcp.md` | Build MCP servers with TS+Bun+ElysiaJS | context7, augment, repomix |
1483
- | Accounts | `accounts.md` | Financial operations | quickfile, augment |
1484
- | AI-DevOps | `aidevops.md` | Framework operations, meta-agents, setup | context7, augment, repomix |
1485
- | Content | `content.md` | Content creation, humanise AI text | augment |
1486
- | Health | `health.md` | Health and wellness guidance | augment |
1487
- | Legal | `legal.md` | Legal compliance and documentation | augment |
1488
- | Marketing | `marketing.md` | Marketing strategy and automation | augment |
1489
- | Research | `research.md` | Research and analysis tasks | context7, augment |
1490
- | Sales | `sales.md` | Sales operations and CRM | augment |
1491
- | SEO | `seo.md` | SEO optimization, Search Console, keyword research | gsc, ahrefs, serper, context7, augment |
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 765+ subagents organized by domain.
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 # 15 primary agents
2132
- │ ├── scripts/ # 172 helper 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.106.0
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.106.0
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
- "beads": {
937
- "enabled": $enable_beads,
938
- "sync_on_commit": false,
939
- "auto_ready_check": true
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,database # Enable multiple"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "2.106.0",
3
+ "version": "2.107.0",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "bin": {
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.106.0
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
- print_info "Clean mode: removing stale files from $target_dir (preserving custom/, draft/)"
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
- local tmp_preserve="$(mktemp -d)"
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 custom/draft agents"
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 custom/draft agents; aborting clean"
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
- if rsync -a --exclude='loop-state/' --exclude='custom/' --exclude='draft/' "$source_dir/" "$target_dir/"; then
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
- if (cd "$source_dir" && tar cf - --exclude='loop-state' --exclude='custom' --exclude='draft' .) | (cd "$target_dir" && tar xf -); then
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..."