From 5c1c4443e7d61056f8e8fc3bf1df3b126e525e0a Mon Sep 17 00:00:00 2001 From: KienBM ubuntu Date: Mon, 16 Mar 2026 04:17:14 +0700 Subject: [PATCH] feat: add --division flag for selective agent installation Add --division flag to install.sh for installing agents from specific divisions only. Also extract shared helpers (slugify, get_field, get_body) into scripts/lib.sh per review feedback. Changes: - scripts/lib.sh: new shared helpers sourced by both scripts - scripts/install.sh: add --division flag, active_divisions(), slug_allowed() - scripts/convert.sh: source lib.sh instead of duplicating helpers Closes #134 --- scripts/convert.sh | 26 ++------------ scripts/install.sh | 90 ++++++++++++++++++++++++++++++++++++++++++---- scripts/lib.sh | 24 +++++++++++++ 3 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 scripts/lib.sh diff --git a/scripts/convert.sh b/scripts/convert.sh index 27d2f66e4..7ad713825 100755 --- a/scripts/convert.sh +++ b/scripts/convert.sh @@ -79,29 +79,9 @@ parallel_jobs_default() { echo 4 } -# --- Frontmatter helpers --- - -# Extract a single field value from YAML frontmatter block. -# Usage: get_field -get_field() { - local field="$1" file="$2" - awk -v f="$field" ' - /^---$/ { fm++; next } - fm == 1 && $0 ~ "^" f ": " { sub("^" f ": ", ""); print; exit } - ' "$file" -} - -# Strip the leading frontmatter block and return only the body. -# Usage: get_body -get_body() { - awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$1" -} - -# Convert a human-readable agent name to a lowercase kebab-case slug. -# "Frontend Developer" → "frontend-developer" -slugify() { - echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' -} +# --- Frontmatter helpers (shared) --- +# shellcheck source=lib.sh +. "$SCRIPT_DIR/lib.sh" # --- Per-tool converters --- diff --git a/scripts/install.sh b/scripts/install.sh index 9bc4f1d89..571a8783a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -7,7 +7,7 @@ # is missing or stale. # # Usage: -# ./scripts/install.sh [--tool ] [--interactive] [--no-interactive] [--parallel] [--jobs N] [--help] +# ./scripts/install.sh [--tool ] [--division ] [--interactive] [--no-interactive] [--parallel] [--jobs N] [--help] # # Tools: # claude-code -- Copy agents to ~/.claude/agents/ @@ -24,6 +24,10 @@ # # Flags: # --tool Install only the specified tool +# --division Install only agents from specified division(s) (comma-separated) +# Valid: design, engineering, game-development, marketing, paid-media, +# sales, product, project-management, testing, support, spatial-computing, +# specialized # --interactive Show interactive selector (default when run in a terminal) # --no-interactive Skip interactive selector, install all detected tools # --parallel Run install for each selected tool in parallel (output order may vary) @@ -103,6 +107,55 @@ INTEGRATIONS="$REPO_ROOT/integrations" ALL_TOOLS=(claude-code copilot antigravity gemini-cli opencode openclaw cursor aider windsurf qwen) +# --------------------------------------------------------------------------- +# Division filtering +# --------------------------------------------------------------------------- +ALL_DIVISIONS=( + design engineering game-development marketing paid-media sales product + project-management testing support spatial-computing specialized +) +FILTER_DIVISIONS=() # empty = all divisions + +# shellcheck source=lib.sh +. "$SCRIPT_DIR/lib.sh" + +# Return the list of division dirs to install (filtered or all) +active_divisions() { + if [[ ${#FILTER_DIVISIONS[@]} -gt 0 ]]; then + printf '%s\n' "${FILTER_DIVISIONS[@]}" + else + printf '%s\n' "${ALL_DIVISIONS[@]}" + fi +} + +# Build a newline-separated list of allowed slugs from active divisions +_allowed_slugs_cache="" +allowed_slugs() { + if [[ -n "$_allowed_slugs_cache" ]]; then + printf '%s' "$_allowed_slugs_cache" + return + fi + local slugs="" div + for div in $(active_divisions); do + [[ -d "$REPO_ROOT/$div" ]] || continue + while IFS= read -r -d '' f; do + local name; name="$(get_field name "$f")" + [[ -n "$name" ]] && slugs+="$(slugify "$name")"$'\n' + done < <(find "$REPO_ROOT/$div" -name "*.md" -type f -print0) + done + _allowed_slugs_cache="$slugs" + printf '%s' "$slugs" +} + +# Check if a slug matches the allowed set (no-op when no filter) +# Handles both raw slugs ("code-reviewer") and prefixed ("agency-code-reviewer") +slug_allowed() { + [[ ${#FILTER_DIVISIONS[@]} -eq 0 ]] && return 0 + local slug="$1" + local stripped="${slug#agency-}" + allowed_slugs | grep -qxF "$stripped" +} + # --------------------------------------------------------------------------- # Usage # --------------------------------------------------------------------------- @@ -298,8 +351,7 @@ install_claude_code() { local count=0 mkdir -p "$dest" local dir f first_line - for dir in academic design engineering game-development marketing paid-media sales product project-management \ - testing support spatial-computing specialized; do + for dir in $(active_divisions); do [[ -d "$REPO_ROOT/$dir" ]] || continue while IFS= read -r -d '' f; do first_line="$(head -1 "$f")" @@ -317,8 +369,7 @@ install_copilot() { local count=0 mkdir -p "$dest_github" "$dest_copilot" local dir f first_line - for dir in academic design engineering game-development marketing paid-media sales product project-management \ - testing support spatial-computing specialized; do + for dir in $(active_divisions); do [[ -d "$REPO_ROOT/$dir" ]] || continue while IFS= read -r -d '' f; do first_line="$(head -1 "$f")" @@ -341,6 +392,7 @@ install_antigravity() { local d while IFS= read -r -d '' d; do local name; name="$(basename "$d")" + slug_allowed "$name" || continue mkdir -p "$dest/$name" cp "$d/SKILL.md" "$dest/$name/SKILL.md" (( count++ )) || true @@ -362,6 +414,7 @@ install_gemini_cli() { local d while IFS= read -r -d '' d; do local name; name="$(basename "$d")" + slug_allowed "$name" || continue mkdir -p "$dest/skills/$name" cp "$d/SKILL.md" "$dest/skills/$name/SKILL.md" (( count++ )) || true @@ -377,6 +430,8 @@ install_opencode() { mkdir -p "$dest" local f while IFS= read -r -d '' f; do + local slug; slug="$(basename "$f" .md)" + slug_allowed "$slug" || continue cp "$f" "$dest/"; (( count++ )) || true done < <(find "$src" -maxdepth 1 -name "*.md" -print0) ok "OpenCode: $count agents -> $dest" @@ -392,6 +447,7 @@ install_openclaw() { local d while IFS= read -r -d '' d; do local name; name="$(basename "$d")" + slug_allowed "$name" || continue mkdir -p "$dest/$name" cp "$d/SOUL.md" "$dest/$name/SOUL.md" cp "$d/AGENTS.md" "$dest/$name/AGENTS.md" @@ -416,6 +472,8 @@ install_cursor() { mkdir -p "$dest" local f while IFS= read -r -d '' f; do + local slug; slug="$(basename "$f" .mdc)" + slug_allowed "$slug" || continue cp "$f" "$dest/"; (( count++ )) || true done < <(find "$src" -maxdepth 1 -name "*.mdc" -print0) ok "Cursor: $count rules -> $dest" @@ -457,8 +515,10 @@ install_qwen() { mkdir -p "$dest" - local f + local f name while IFS= read -r -d '' f; do + name="$(basename "$f" .md)" + slug_allowed "$name" || continue cp "$f" "$dest/" (( count++ )) || true done < <(find "$src" -maxdepth 1 -name "*.md" -print0) @@ -496,6 +556,21 @@ main() { while [[ $# -gt 0 ]]; do case "$1" in --tool) tool="${2:?'--tool requires a value'}"; shift 2; interactive_mode="no" ;; + --division) + local divs="${2:?'--division requires a value'}" + IFS=',' read -ra _divs <<< "$divs" + local _d + for _d in "${_divs[@]}"; do + _d="$(echo "$_d" | xargs)" # trim whitespace + local valid_div=false _ad + for _ad in "${ALL_DIVISIONS[@]}"; do [[ "$_ad" == "$_d" ]] && valid_div=true && break; done + if ! $valid_div; then + err "Unknown division '$_d'. Valid: ${ALL_DIVISIONS[*]}" + exit 1 + fi + FILTER_DIVISIONS+=("$_d") + done + shift 2 ;; --interactive) interactive_mode="yes"; shift ;; --no-interactive) interactive_mode="no"; shift ;; --parallel) use_parallel=true; shift ;; @@ -574,6 +649,9 @@ main() { if $use_parallel; then ok "Installing $n_selected tools in parallel (output buffered per tool)." fi + if [[ ${#FILTER_DIVISIONS[@]} -gt 0 ]]; then + printf " Divisions: %s\n" "${FILTER_DIVISIONS[*]}" + fi printf "\n" local installed=0 t i=0 diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100644 index 000000000..db55192b9 --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Shared helpers for convert.sh and install.sh + +# Extract a YAML frontmatter field value from an agent .md file. +# Usage: get_field +get_field() { + local field="$1" file="$2" + awk -v f="$field" ' + /^---$/ { fm++; next } + fm == 1 && $0 ~ "^" f ": " { sub("^" f ": ", ""); print; exit } + ' "$file" +} + +# Strip the leading frontmatter block and return only the body. +# Usage: get_body +get_body() { + awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$1" +} + +# Convert a human-readable agent name to a lowercase kebab-case slug. +# "Frontend Developer" → "frontend-developer" +slugify() { + echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' +}