#!/usr/bin/env bash # # convert.sh — Convert agency agent .md files into tool-specific formats. # # Reads all agent files from the standard category directories and outputs # converted files to integrations//. Run this to regenerate all # integration files after adding or modifying agents. # # Usage: # ./scripts/convert.sh [--tool ] [--out ] [--parallel] [--jobs N] [--help] # # Tools: # antigravity — Antigravity skill files (~/.gemini/antigravity/skills/) # gemini-cli — Gemini CLI subagent files (~/.gemini/agents/*.md) # opencode — OpenCode agent files (.opencode/agents/*.md) # cursor — Cursor rule files (.cursor/rules/*.mdc) # aider — Single CONVENTIONS.md for Aider # windsurf — Single .windsurfrules for Windsurf # openclaw — OpenClaw workspaces (integrations/openclaw//SOUL.md) # qwen — Qwen Code SubAgent files (~/.qwen/agents/*.md) # kimi — Kimi Code CLI agent files (~/.config/kimi/agents/) # codex — Codex custom agent TOML files (~/.codex/agents/*.toml) # osaurus — Osaurus skill files (~/.osaurus/skills//SKILL.md) # all — All tools (default) # # Output is written to integrations// relative to the repo root. # This script never touches user config dirs — see install.sh for that. # # --parallel When tool is 'all', run independent tools in parallel (output order may vary). # --jobs N Max parallel jobs when using --parallel (default: nproc or 4). set -euo pipefail # --- Colour helpers --- if [[ -t 1 && -z "${NO_COLOR:-}" && "${TERM:-}" != "dumb" ]]; then GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; RED=$'\033[0;31m'; BOLD=$'\033[1m'; RESET=$'\033[0m' else GREEN=''; YELLOW=''; RED=''; BOLD=''; RESET='' fi info() { printf "${GREEN}[OK]${RESET} %s\n" "$*"; } warn() { printf "${YELLOW}[!!]${RESET} %s\n" "$*"; } error() { printf "${RED}[ERR]${RESET} %s\n" "$*" >&2; } header() { echo -e "\n${BOLD}$*${RESET}"; } # Progress bar: [=======> ] 3/8 (tqdm-style) progress_bar() { local current="$1" total="$2" width="${3:-20}" i filled empty (( total > 0 )) || return filled=$(( width * current / total )) empty=$(( width - filled )) printf "\r [" for (( i=0; i"; (( empty-- )); fi for (( i=0; i/dev/null) && [[ -n "$n" ]] && echo "$n" && return n=$(sysctl -n hw.ncpu 2>/dev/null) && [[ -n "$n" ]] && echo "$n" && return echo 4 } # --- Frontmatter helpers: get_field / get_body / slugify now live in lib.sh --- # Escape a value for a TOML basic string, including control characters that # cannot appear raw in TOML source. toml_escape_string() { printf '%s' "$1" | perl -0pe ' s/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g; s/\r/\\r/g; s/\t/\\t/g; s/\f/\\f/g; s/\x08/\\b/g; s/([\x00-\x07\x0B\x0E-\x1F\x7F])/sprintf("\\u%04X", ord($1))/ge; ' } # --- Per-tool converters --- convert_antigravity() { local file="$1" local name description slug outdir outfile body name="$(get_field "name" "$file")" description="$(get_field "description" "$file")" slug="agency-$(slugify "$name")" body="$(get_body "$file")" outdir="$OUT_DIR/antigravity/$slug" outfile="$outdir/SKILL.md" mkdir -p "$outdir" # Antigravity SKILL.md format mirrors community skills in ~/.gemini/antigravity/skills/ cat > "$outfile" </). outdir="$OUT_DIR/osaurus/$slug" outfile="$outdir/SKILL.md" mkdir -p "$outdir" # Osaurus skill format: the Anthropic "Agent Skills" SKILL.md — a directory # named for the skill containing a SKILL.md with name + description frontmatter # and the persona as the instruction body. Installs into ~/.osaurus/skills/. # Kept to the standard fields so it stays compatible with any Agent-Skills host. cat > "$outfile" < "$outfile" < "$outfile" < "$outfile" < "$outfile" < "$outdir/SOUL.md" < "$outdir/AGENTS.md" < "$outdir/IDENTITY.md" < "$outdir/IDENTITY.md" < "$outfile" < "$outfile" < "$agent_file" < "$outdir/system.md" < "$AIDER_TMP" <<'HEREDOC' # The Agency — AI Agent Conventions # # This file provides Aider with the full roster of specialized AI agents from # The Agency (https://github.com/msitarzewski/agency-agents). # # To activate an agent, reference it by name in your Aider session prompt, e.g.: # "Use the Frontend Developer agent to review this component." # # Generated by scripts/convert.sh — do not edit manually. HEREDOC cat > "$WINDSURF_TMP" <<'HEREDOC' # The Agency — AI Agent Rules for Windsurf # # Full roster of specialized AI agents from The Agency. # To activate an agent, reference it by name in your Windsurf conversation. # # Generated by scripts/convert.sh — do not edit manually. HEREDOC accumulate_aider() { local file="$1" local name description body name="$(get_field "name" "$file")" description="$(get_field "description" "$file")" body="$(get_body "$file")" cat >> "$AIDER_TMP" < ${description} ${body} HEREDOC } accumulate_windsurf() { local file="$1" local name description body name="$(get_field "name" "$file")" description="$(get_field "description" "$file")" body="$(get_body "$file")" cat >> "$WINDSURF_TMP" </ for conversion targets. clean_tool_output() { local dir="$OUT_DIR/$1" [[ -d "$dir" ]] || return 0 find "$dir" -mindepth 1 -maxdepth 1 ! -name 'README.md' -exec rm -rf {} + } run_conversions() { local tool="$1" local count=0 clean_tool_output "$tool" for dir in "${AGENT_DIRS[@]}"; do local dirpath="$REPO_ROOT/$dir" [[ -d "$dirpath" ]] || continue while IFS= read -r -d '' file; do # Skip files without frontmatter (non-agent docs like QUICKSTART.md) local first_line first_line="$(head -1 "$file")" [[ "$first_line" == "---" ]] || continue local name name="$(get_field "name" "$file")" [[ -n "$name" ]] || continue case "$tool" in antigravity) convert_antigravity "$file" ;; codex) convert_codex "$file" ;; gemini-cli) convert_gemini_cli "$file" ;; opencode) convert_opencode "$file" ;; cursor) convert_cursor "$file" ;; openclaw) convert_openclaw "$file" ;; qwen) convert_qwen "$file" ;; kimi) convert_kimi "$file" ;; osaurus) convert_osaurus "$file" ;; aider) accumulate_aider "$file" ;; windsurf) accumulate_windsurf "$file" ;; esac (( count++ )) || true done < <(find "$dirpath" -name "*.md" -type f -print0 | sort -z) done echo "$count" } # --- Entry point --- main() { local tool="all" local use_parallel=false local parallel_jobs parallel_jobs="$(parallel_jobs_default)" while [[ $# -gt 0 ]]; do case "$1" in --tool) tool="${2:?'--tool requires a value'}"; shift 2 ;; --out) OUT_DIR="${2:?'--out requires a value'}"; shift 2 ;; --parallel) use_parallel=true; shift ;; --jobs) parallel_jobs="${2:?'--jobs requires a value'}"; shift 2 ;; --help|-h) usage ;; *) error "Unknown option: $1"; usage ;; esac done local valid_tools=("antigravity" "gemini-cli" "opencode" "cursor" "aider" "windsurf" "openclaw" "qwen" "kimi" "codex" "osaurus" "all") local valid=false for t in "${valid_tools[@]}"; do [[ "$t" == "$tool" ]] && valid=true && break; done if ! $valid; then error "Unknown tool '$tool'. Valid: ${valid_tools[*]}" exit 1 fi header "The Agency -- Converting agents to tool-specific formats" echo " Repo: $REPO_ROOT" echo " Output: $OUT_DIR" echo " Tool: $tool" echo " Date: $TODAY" if $use_parallel && [[ "$tool" == "all" ]]; then info "Parallel mode: output buffered so each tool's output stays together." fi local tools_to_run=() if [[ "$tool" == "all" ]]; then tools_to_run=("antigravity" "gemini-cli" "opencode" "cursor" "aider" "windsurf" "openclaw" "qwen" "kimi" "codex" "osaurus") else tools_to_run=("$tool") fi local total=0 local n_tools=${#tools_to_run[@]} if $use_parallel && [[ "$tool" == "all" ]]; then # Tools that write to separate dirs can run in parallel; buffer output so each tool's output stays together local parallel_tools=(antigravity gemini-cli opencode cursor openclaw qwen codex osaurus) local parallel_out_dir parallel_out_dir="$(mktemp -d)" info "Converting: ${#parallel_tools[@]}/${n_tools} tools in parallel (output buffered per tool)..." export AGENCY_CONVERT_OUT_DIR="$parallel_out_dir" export AGENCY_CONVERT_SCRIPT="$SCRIPT_DIR/convert.sh" export AGENCY_CONVERT_OUT="$OUT_DIR" printf '%s\n' "${parallel_tools[@]}" | xargs -P "$parallel_jobs" -I {} sh -c '"$AGENCY_CONVERT_SCRIPT" --tool "{}" --out "$AGENCY_CONVERT_OUT" > "$AGENCY_CONVERT_OUT_DIR/{}" 2>&1' for t in "${parallel_tools[@]}"; do [[ -f "$parallel_out_dir/$t" ]] && cat "$parallel_out_dir/$t" done rm -rf "$parallel_out_dir" local idx=8 for t in aider windsurf; do progress_bar "$idx" "$n_tools" printf "\n" header "Converting: $t ($idx/$n_tools)" local count count="$(run_conversions "$t")" total=$(( total + count )) info "Converted $count agents for $t" (( idx++ )) || true done else local i=0 for t in "${tools_to_run[@]}"; do (( i++ )) || true progress_bar "$i" "$n_tools" printf "\n" header "Converting: $t ($i/$n_tools)" local count count="$(run_conversions "$t")" total=$(( total + count )) info "Converted $count agents for $t" done fi # Write single-file outputs after accumulation if [[ "$tool" == "all" || "$tool" == "aider" ]]; then mkdir -p "$OUT_DIR/aider" cp "$AIDER_TMP" "$OUT_DIR/aider/CONVENTIONS.md" info "Wrote integrations/aider/CONVENTIONS.md" fi if [[ "$tool" == "all" || "$tool" == "windsurf" ]]; then mkdir -p "$OUT_DIR/windsurf" cp "$WINDSURF_TMP" "$OUT_DIR/windsurf/.windsurfrules" info "Wrote integrations/windsurf/.windsurfrules" fi echo "" if $use_parallel && [[ "$tool" == "all" ]]; then info "Done. $n_tools tools (parallel; total conversions not aggregated)." else info "Done. Total conversions: $total" fi } main "$@"