feat: add 'aidevops plugin' CLI commands for plugin management (t136.2)#759
Conversation
Add cmd_plugin() to aidevops.sh with add/list/update/enable/disable/remove subcommands. Plugin state stored in ~/.config/aidevops/plugins.json. Namespace validation prevents collisions with core directories. Update plugins.md to reflect CLI availability. Fixes: t136.2 ref:GH#729
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
WalkthroughPlugin management documentation updated to describe global state storage in Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/CLI
participant Validator as Validator
participant Git as Git Operations
participant FileSystem as File System
participant Config as Config Manager
User->>Validator: aidevops plugin add --repo <url>
Validator->>Validator: Derive/validate namespace
Validator->>Validator: Check reserved names
Validator-->>User: Namespace valid
User->>Git: Clone plugin repository
Git->>FileSystem: Extract to ~/.aidevops/agents/<namespace>
Git-->>User: Clone complete
User->>Config: Write plugin entry to plugins.json
Config->>FileSystem: Update ~/.config/aidevops/plugins.json
Config-->>User: Plugin registered
User->>FileSystem: Enable flag set (enabled=true)
FileSystem-->>User: Plugin ready
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Mon Feb 9 04:40:27 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@aidevops.sh`:
- Around line 2169-2176: The argument-parsing case block (handling
--namespace|--ns, --branch|-b, --name|-n) can attempt to read $2 and shift 2
when a value is missing, causing a failure under set -euo pipefail; update each
flag branch to first verify that $# -ge 2 (or that $2 is non-empty) before
assigning namespace/branch/plugin_name and shifting, and if the value is missing
call print_error with a descriptive "missing value for <flag>" message and
return 1 so the script exits cleanly instead of failing on shift.
- Around line 2259-2269: The data rows are double-indented because the jq output
includes a leading two-space prefix; update the jq invocation that builds the
tab-separated line (the command reading from "$plugins_file" using jq -r
'.plugins[] | " \(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"') to
remove the leading two spaces so it emits '\(.name)\t\(.namespace)\t\(.enabled
// true)\t\(.repo)'; keep the downstream while IFS=$'\t' read -r name ns enabled
repo and the printf that prints headers and rows unchanged so header and data
indentation match.
- Around line 2286-2298: The rm -rf calls are vulnerable when $ns (from
get_plugin_field) is empty and could delete $agents_dir; update every removal
site (the clone_dir removal in the update flow and the similar
remove/disable/bulk-update loops) to guard the variable using the parameter
expansion form so it fails fast (e.g., use ${ns:?} or ${clone_dir:?} when
constructing the path) before running rm -rf; ensure you reference the same
variables used in the code paths (ns, clone_dir, agents_dir) and apply the guard
consistently around the rm -rf invocations to prevent accidental deletion.
- Around line 2355-2363: The plugin JSON is being updated to set (.plugins[] |
select(.name == $n)).enabled = true (tmp_file/plugins_file) before verifying
deployment, so if git clone (git clone --branch "$target_branch" --depth 1
"$target_repo" "$agents_dir/$target_ns") fails (currently silenced by
2>/dev/null) the plugin appears enabled but has no files; change the flow in the
function to capture the git clone exit status and stderr, do not redirect errors
to /dev/null, and on clone failure log/print a clear warning including the git
error, revert the JSON change (move tmp_file backup or reset the .enabled flag
for target_name) or skip marking enabled until after a successful clone, and
ensure you remove any partially created directory (agents_dir/$target_ns) on
failure.
🧹 Nitpick comments (2)
aidevops.sh (2)
2094-2111: Missingjqavailability check — every other command in this script validates it.
cmd_pluginunconditionally callsjqthroughout (viaget_plugin_field, list, add, remove, etc.), but never checkscommand -v jq. The rest of the script (e.g.,cmd_repos,register_repo) consistently gates on jq availability and prints a helpful message. A missingjqhere would produce cryptic "command not found" errors.🔧 Add jq gate early in cmd_plugin
# Ensure config dir exists mkdir -p "$CONFIG_DIR" + # jq is required for plugin management + if ! command -v jq &>/dev/null; then + print_error "jq is required for plugin management" + print_info "Install jq: https://jqlang.github.io/jq/download/" + return 1 + fi + # Initialize plugins.json if missing if [[ ! -f "$plugins_file" ]]; then
2219-2225: Inconsistent stderr handling:addleaks clone output,updatesuppresses it.Line 2221 uses
2>&1(merging stderr into stdout and displaying it), while the equivalent clone in the bulk-update path (line 2319) uses2>/dev/null. The single-plugin update (line 2291) also uses2>&1. Pick one approach — either show progress consistently or suppress consistently. Suppressing with2>/dev/nulland relying on exit-code messaging is cleaner given theprint_success/print_errorpattern already in use.
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --namespace|--ns) namespace="$2"; shift 2 ;; | ||
| --branch|-b) branch="$2"; shift 2 ;; | ||
| --name|-n) plugin_name="$2"; shift 2 ;; | ||
| *) print_error "Unknown option: $1"; return 1 ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
Option parsing will error out if a flag is passed without its value.
Under set -euo pipefail (line 8), if a user runs aidevops plugin add <url> --namespace (missing value), $2 is unset and shift 2 fails. Consider validating that $# ≥ 2 before consuming the value, or catch the missing-argument case.
🔧 Example guard
while [[ $# -gt 0 ]]; do
case "$1" in
- --namespace|--ns) namespace="$2"; shift 2 ;;
- --branch|-b) branch="$2"; shift 2 ;;
- --name|-n) plugin_name="$2"; shift 2 ;;
+ --namespace|--ns) [[ $# -ge 2 ]] || { print_error "Missing value for $1"; return 1; }; namespace="$2"; shift 2 ;;
+ --branch|-b) [[ $# -ge 2 ]] || { print_error "Missing value for $1"; return 1; }; branch="$2"; shift 2 ;;
+ --name|-n) [[ $# -ge 2 ]] || { print_error "Missing value for $1"; return 1; }; plugin_name="$2"; shift 2 ;;
*) print_error "Unknown option: $1"; return 1 ;;
esac
done📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --namespace|--ns) namespace="$2"; shift 2 ;; | |
| --branch|-b) branch="$2"; shift 2 ;; | |
| --name|-n) plugin_name="$2"; shift 2 ;; | |
| *) print_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --namespace|--ns) [[ $# -ge 2 ]] || { print_error "Missing value for $1"; return 1; }; namespace="$2"; shift 2 ;; | |
| --branch|-b) [[ $# -ge 2 ]] || { print_error "Missing value for $1"; return 1; }; branch="$2"; shift 2 ;; | |
| --name|-n) [[ $# -ge 2 ]] || { print_error "Missing value for $1"; return 1; }; plugin_name="$2"; shift 2 ;; | |
| *) print_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done |
🤖 Prompt for AI Agents
In `@aidevops.sh` around lines 2169 - 2176, The argument-parsing case block
(handling --namespace|--ns, --branch|-b, --name|-n) can attempt to read $2 and
shift 2 when a value is missing, causing a failure under set -euo pipefail;
update each flag branch to first verify that $# -ge 2 (or that $2 is non-empty)
before assigning namespace/branch/plugin_name and shifting, and if the value is
missing call print_error with a descriptive "missing value for <flag>" message
and return 1 so the script exits cleanly instead of failing on shift.
| printf " %-15s %-10s %-8s %s\n" "NAME" "NAMESPACE" "ENABLED" "REPO" | ||
| printf " %-15s %-10s %-8s %s\n" "----" "---------" "-------" "----" | ||
|
|
||
| jq -r '.plugins[] | " \(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"' "$plugins_file" 2>/dev/null | \ | ||
| while IFS=$'\t' read -r name ns enabled repo; do | ||
| local status_icon="yes" | ||
| if [[ "$enabled" == "false" ]]; then | ||
| status_icon="no" | ||
| fi | ||
| printf " %-15s %-10s %-8s %s\n" "$name" "$ns" "$status_icon" "$repo" | ||
| done |
There was a problem hiding this comment.
List output formatting: double indentation on data rows.
The jq template on line 2262 emits " \(.name)\t..." (2-space prefix), and then read assigns that prefixed string to $name. The subsequent printf on line 2268 adds another 2-space prefix, so data rows will be indented 4 spaces while headers (lines 2259-2260) are indented 2. Remove the leading spaces from the jq template.
🔧 Fix alignment
- jq -r '.plugins[] | " \(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"' "$plugins_file" 2>/dev/null | \
+ jq -r '.plugins[] | "\(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"' "$plugins_file" 2>/dev/null | \📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| printf " %-15s %-10s %-8s %s\n" "NAME" "NAMESPACE" "ENABLED" "REPO" | |
| printf " %-15s %-10s %-8s %s\n" "----" "---------" "-------" "----" | |
| jq -r '.plugins[] | " \(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"' "$plugins_file" 2>/dev/null | \ | |
| while IFS=$'\t' read -r name ns enabled repo; do | |
| local status_icon="yes" | |
| if [[ "$enabled" == "false" ]]; then | |
| status_icon="no" | |
| fi | |
| printf " %-15s %-10s %-8s %s\n" "$name" "$ns" "$status_icon" "$repo" | |
| done | |
| printf " %-15s %-10s %-8s %s\n" "NAME" "NAMESPACE" "ENABLED" "REPO" | |
| printf " %-15s %-10s %-8s %s\n" "----" "---------" "-------" "----" | |
| jq -r '.plugins[] | "\(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)"' "$plugins_file" 2>/dev/null | \ | |
| while IFS=$'\t' read -r name ns enabled repo; do | |
| local status_icon="yes" | |
| if [[ "$enabled" == "false" ]]; then | |
| status_icon="no" | |
| fi | |
| printf " %-15s %-10s %-8s %s\n" "$name" "$ns" "$status_icon" "$repo" | |
| done |
🤖 Prompt for AI Agents
In `@aidevops.sh` around lines 2259 - 2269, The data rows are double-indented
because the jq output includes a leading two-space prefix; update the jq
invocation that builds the tab-separated line (the command reading from
"$plugins_file" using jq -r '.plugins[] | " \(.name)\t\(.namespace)\t\(.enabled
// true)\t\(.repo)"') to remove the leading two spaces so it emits
'\(.name)\t\(.namespace)\t\(.enabled // true)\t\(.repo)'; keep the downstream
while IFS=$'\t' read -r name ns enabled repo and the printf that prints headers
and rows unchanged so header and data indentation match.
| fi | ||
|
|
||
| print_info "Updating plugin '$target'..." | ||
| local clone_dir="$agents_dir/$ns" | ||
| rm -rf "$clone_dir" | ||
| if git clone --branch "$branch_name" --depth 1 "$repo" "$clone_dir" 2>&1; then | ||
| rm -rf "$clone_dir/.git" | ||
| print_success "Plugin '$target' updated" | ||
| else | ||
| print_error "Failed to update plugin '$target'" | ||
| return 1 | ||
| fi | ||
| else |
There was a problem hiding this comment.
Guard rm -rf against empty namespace to prevent catastrophic deletion.
If get_plugin_field returns an empty string (e.g., jq not installed or JSON parse failure), $ns is empty and rm -rf "$agents_dir/" would wipe the entire agents directory. The same pattern appears in the bulk-update loop (line 2318), disable (line 2389), and remove (line 2411). The static analysis tool flagged lines 2389 and 2411 with SC2115, but the issue is present in all four rm -rf sites.
🛡️ Proposed fix — use ${var:?} guards on all rm -rf calls
print_info "Updating plugin '$target'..."
local clone_dir="$agents_dir/$ns"
- rm -rf "$clone_dir"
+ rm -rf "${clone_dir:?}"
if git clone --branch "$branch_name" --depth 1 "$repo" "$clone_dir" 2>&1; then local pdir="$agents_dir/$pns"
- rm -rf "$pdir"
+ rm -rf "${pdir:?}"
if git clone --branch "$pbranch" --depth 1 "$prepo" "$pdir" 2>/dev/null; then # Remove deployed files
if [[ -d "$agents_dir/$target_ns" ]]; then
- rm -rf "$agents_dir/$target_ns"
+ rm -rf "${agents_dir:?}/${target_ns:?}"
fi(Apply to both the disable block at line 2389 and the remove block at line 2411.)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fi | |
| print_info "Updating plugin '$target'..." | |
| local clone_dir="$agents_dir/$ns" | |
| rm -rf "$clone_dir" | |
| if git clone --branch "$branch_name" --depth 1 "$repo" "$clone_dir" 2>&1; then | |
| rm -rf "$clone_dir/.git" | |
| print_success "Plugin '$target' updated" | |
| else | |
| print_error "Failed to update plugin '$target'" | |
| return 1 | |
| fi | |
| else | |
| fi | |
| print_info "Updating plugin '$target'..." | |
| local clone_dir="$agents_dir/$ns" | |
| rm -rf "${clone_dir:?}" | |
| if git clone --branch "$branch_name" --depth 1 "$repo" "$clone_dir" 2>&1; then | |
| rm -rf "$clone_dir/.git" | |
| print_success "Plugin '$target' updated" | |
| else | |
| print_error "Failed to update plugin '$target'" | |
| return 1 | |
| fi | |
| else |
🤖 Prompt for AI Agents
In `@aidevops.sh` around lines 2286 - 2298, The rm -rf calls are vulnerable when
$ns (from get_plugin_field) is empty and could delete $agents_dir; update every
removal site (the clone_dir removal in the update flow and the similar
remove/disable/bulk-update loops) to guard the variable using the parameter
expansion form so it fails fast (e.g., use ${ns:?} or ${clone_dir:?} when
constructing the path) before running rm -rf; ensure you reference the same
variables used in the code paths (ns, clone_dir, agents_dir) and apply the guard
consistently around the rm -rf invocations to prevent accidental deletion.
| local tmp_file="${plugins_file}.tmp" | ||
| jq --arg n "$target_name" '(.plugins[] | select(.name == $n)).enabled = true' "$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file" | ||
|
|
||
| # Deploy if not already present | ||
| if [[ ! -d "$agents_dir/$target_ns" ]]; then | ||
| print_info "Deploying plugin '$target_name'..." | ||
| if git clone --branch "$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns" 2>/dev/null; then | ||
| rm -rf "$agents_dir/$target_ns/.git" | ||
| fi |
There was a problem hiding this comment.
Silent failure when enable clone fails — plugin marked enabled but not deployed.
If git clone fails on line 2361, the error is suppressed (2>/dev/null), the function continues, and the plugin is reported as "enabled" (line 2366) despite having no deployed files. Consider checking the clone exit code and warning the user.
🔧 Proposed fix
# Deploy if not already present
if [[ ! -d "$agents_dir/$target_ns" ]]; then
print_info "Deploying plugin '$target_name'..."
- if git clone --branch "$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns" 2>/dev/null; then
+ if git clone --branch "$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns" 2>&1; then
rm -rf "$agents_dir/$target_ns/.git"
+ else
+ print_error "Failed to deploy plugin '$target_name' — enabled in config but files not deployed"
+ return 1
fi
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| local tmp_file="${plugins_file}.tmp" | |
| jq --arg n "$target_name" '(.plugins[] | select(.name == $n)).enabled = true' "$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file" | |
| # Deploy if not already present | |
| if [[ ! -d "$agents_dir/$target_ns" ]]; then | |
| print_info "Deploying plugin '$target_name'..." | |
| if git clone --branch "$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns" 2>/dev/null; then | |
| rm -rf "$agents_dir/$target_ns/.git" | |
| fi | |
| local tmp_file="${plugins_file}.tmp" | |
| jq --arg n "$target_name" '(.plugins[] | select(.name == $n)).enabled = true' "$plugins_file" > "$tmp_file" && mv "$tmp_file" "$plugins_file" | |
| # Deploy if not already present | |
| if [[ ! -d "$agents_dir/$target_ns" ]]; then | |
| print_info "Deploying plugin '$target_name'..." | |
| if git clone --branch "$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns" 2>&1; then | |
| rm -rf "$agents_dir/$target_ns/.git" | |
| else | |
| print_error "Failed to deploy plugin '$target_name' — enabled in config but files not deployed" | |
| return 1 | |
| fi |
🤖 Prompt for AI Agents
In `@aidevops.sh` around lines 2355 - 2363, The plugin JSON is being updated to
set (.plugins[] | select(.name == $n)).enabled = true (tmp_file/plugins_file)
before verifying deployment, so if git clone (git clone --branch
"$target_branch" --depth 1 "$target_repo" "$agents_dir/$target_ns") fails
(currently silenced by 2>/dev/null) the plugin appears enabled but has no files;
change the flow in the function to capture the git clone exit status and stderr,
do not redirect errors to /dev/null, and on clone failure log/print a clear
warning including the git error, revert the JSON change (move tmp_file backup or
reset the .enabled flag for target_name) or skip marking enabled until after a
successful clone, and ensure you remove any partially created directory
(agents_dir/$target_ns) on failure.



Summary
cmd_plugin()toaidevops.shwith 6 subcommands:add,list,update,enable,disable,remove~/.config/aidevops/plugins.jsonwith namespace isolationplugins.mdto reflect CLI availabilityDetails
Part of the Plugin System plan (t136). Follows t136.1 (plugin schema in
.aidevops.json, PR #755).Subcommands
aidevops plugin add <repo-url> [--namespace <name>] [--branch <branch>]aidevops plugin listaidevops plugin update [name]aidevops plugin enable <name>aidevops plugin disable <name>aidevops plugin remove <name>Testing
bash -n aidevops.sh— syntax OKaidevops plugin help— displays correctlyaidevops plugin list— shows "No plugins installed"aidevops help— includes plugin in commands listFixes: t136.2 ref:GH#729
Summary by CodeRabbit
New Features
~/.config/aidevops/plugins.jsonwith per-project awareness via.aidevops.jsonaidevops plugin helpDocumentation