convert.sh 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. #!/usr/bin/env bash
  2. #
  3. # convert.sh — Convert agency agent .md files into tool-specific formats.
  4. #
  5. # Reads all agent files from the standard category directories and outputs
  6. # converted files to integrations/<tool>/. Run this to regenerate all
  7. # integration files after adding or modifying agents.
  8. #
  9. # Usage:
  10. # ./scripts/convert.sh [--tool <name>] [--out <dir>] [--parallel] [--jobs N] [--help]
  11. #
  12. # Tools:
  13. # antigravity — Antigravity skill files (~/.gemini/config/skills/)
  14. # gemini-cli — Gemini CLI subagent files (~/.gemini/agents/*.md)
  15. # opencode — OpenCode agent files (.opencode/agents/*.md)
  16. # cursor — Cursor rule files (.cursor/rules/*.mdc)
  17. # aider — Single CONVENTIONS.md for Aider
  18. # windsurf — Single .windsurfrules for Windsurf
  19. # openclaw — OpenClaw workspaces (integrations/openclaw/<agent>/SOUL.md)
  20. # qwen — Qwen Code SubAgent files (~/.qwen/agents/*.md)
  21. # kimi — Kimi Code CLI agent files (~/.config/kimi/agents/)
  22. # codex — Codex custom agent TOML files (~/.codex/agents/*.toml)
  23. # osaurus — Osaurus skill files (~/.osaurus/skills/<name>/SKILL.md)
  24. # hermes — Hermes lazy-router plugin (one plugin + on-disk agent index)
  25. # vibe — Mistral Vibe agent TOML + prompt files (~/.vibe/agents/*.toml + ~/.vibe/prompts/*.md)
  26. # all — All tools (default)
  27. #
  28. # Output is written to integrations/<tool>/ relative to the repo root.
  29. # This script never touches user config dirs — see install.sh for that.
  30. #
  31. # --parallel When tool is 'all', run independent tools in parallel (output order may vary).
  32. # --jobs N Max parallel jobs when using --parallel (default: nproc or 4).
  33. set -euo pipefail
  34. # --- Colour helpers ---
  35. if [[ -t 1 && -z "${NO_COLOR:-}" && "${TERM:-}" != "dumb" ]]; then
  36. GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; RED=$'\033[0;31m'; BOLD=$'\033[1m'; RESET=$'\033[0m'
  37. else
  38. GREEN=''; YELLOW=''; RED=''; BOLD=''; RESET=''
  39. fi
  40. info() { printf "${GREEN}[OK]${RESET} %s\n" "$*"; }
  41. warn() { printf "${YELLOW}[!!]${RESET} %s\n" "$*"; }
  42. error() { printf "${RED}[ERR]${RESET} %s\n" "$*" >&2; }
  43. header() { echo -e "\n${BOLD}$*${RESET}"; }
  44. # Progress bar: [=======> ] 3/8 (tqdm-style)
  45. progress_bar() {
  46. local current="$1" total="$2" width="${3:-20}" i filled empty
  47. (( total > 0 )) || return
  48. filled=$(( width * current / total ))
  49. empty=$(( width - filled ))
  50. printf "\r ["
  51. for (( i=0; i<filled; i++ )); do printf "="; done
  52. if (( filled < width )); then printf ">"; (( empty-- )); fi
  53. for (( i=0; i<empty; i++ )); do printf " "; done
  54. printf "] %s/%s" "$current" "$total"
  55. [[ -t 1 ]] || printf "\n"
  56. }
  57. # --- Paths ---
  58. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  59. REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
  60. OUT_DIR="$REPO_ROOT/integrations"
  61. TODAY="$(date +%Y-%m-%d)"
  62. # Shared helpers (get_field, get_body, slugify, ...)
  63. # shellcheck source=lib.sh
  64. . "$SCRIPT_DIR/lib.sh"
  65. AGENT_DIRS=(
  66. academic design engineering finance game-development gis healthcare marketing paid-media product project-management
  67. sales security spatial-computing specialized support testing
  68. )
  69. # --- Usage ---
  70. usage() {
  71. sed -n '3,27p' "$0" | sed 's/^# \{0,1\}//'
  72. exit 0
  73. }
  74. # Default parallel job count (nproc on Linux; sysctl on macOS when nproc missing)
  75. parallel_jobs_default() {
  76. local n
  77. n=$(nproc 2>/dev/null) && [[ -n "$n" ]] && echo "$n" && return
  78. n=$(sysctl -n hw.ncpu 2>/dev/null) && [[ -n "$n" ]] && echo "$n" && return
  79. echo 4
  80. }
  81. # --- Frontmatter helpers: get_field / get_body / slugify now live in lib.sh ---
  82. # Escape a value for a TOML basic string, including control characters that
  83. # cannot appear raw in TOML source.
  84. toml_escape_string() {
  85. printf '%s' "$1" | perl -0pe '
  86. s/\\/\\\\/g;
  87. s/"/\\"/g;
  88. s/\n/\\n/g;
  89. s/\r/\\r/g;
  90. s/\t/\\t/g;
  91. s/\f/\\f/g;
  92. s/\x08/\\b/g;
  93. s/([\x00-\x07\x0B\x0E-\x1F\x7F])/sprintf("\\u%04X", ord($1))/ge;
  94. '
  95. }
  96. # --- Per-tool converters ---
  97. convert_antigravity() {
  98. local file="$1"
  99. local name description slug outdir outfile body
  100. name="$(get_field "name" "$file")"
  101. description="$(get_field "description" "$file")"
  102. slug="agency-$(slugify "$name")"
  103. body="$(get_body "$file")"
  104. outdir="$OUT_DIR/antigravity/$slug"
  105. outfile="$outdir/SKILL.md"
  106. mkdir -p "$outdir"
  107. # Antigravity Agent-Skills SKILL.md — name + description frontmatter and the
  108. # persona as the body, installed into ~/.gemini/config/skills/ (global) or
  109. # <project>/.agents/skills/ (project). Standard fields only, so it stays a
  110. # valid Agent-Skills skill for any host (and deterministic — no date stamp).
  111. cat > "$outfile" <<HEREDOC
  112. ---
  113. name: ${slug}
  114. description: ${description}
  115. ---
  116. ${body}
  117. HEREDOC
  118. }
  119. convert_osaurus() {
  120. local file="$1"
  121. local name description slug outdir outfile body
  122. name="$(get_field "name" "$file")"
  123. description="$(get_field "description" "$file")"
  124. slug="agency-$(slugify "$name")"
  125. body="$(get_body "$file")"
  126. # Stage one dir per skill (install.sh copies into ~/.osaurus/skills/<name>/).
  127. outdir="$OUT_DIR/osaurus/$slug"
  128. outfile="$outdir/SKILL.md"
  129. mkdir -p "$outdir"
  130. # Osaurus skill format: the Anthropic "Agent Skills" SKILL.md — a directory
  131. # named for the skill containing a SKILL.md with name + description frontmatter
  132. # and the persona as the instruction body. Installs into ~/.osaurus/skills/.
  133. # Kept to the standard fields so it stays compatible with any Agent-Skills host.
  134. cat > "$outfile" <<HEREDOC
  135. ---
  136. name: ${slug}
  137. description: ${description}
  138. ---
  139. ${body}
  140. HEREDOC
  141. }
  142. convert_codex() {
  143. local file="$1"
  144. local name description slug outfile body
  145. name="$(get_field "name" "$file")"
  146. description="$(get_field "description" "$file")"
  147. slug="$(slugify "$name")"
  148. body="$(get_body "$file")"
  149. outfile="$OUT_DIR/codex/agents/${slug}.toml"
  150. mkdir -p "$(dirname "$outfile")"
  151. # Codex custom agent format: one TOML file per agent with minimal required
  152. # fields only. Use a TOML basic string so control characters in the source
  153. # body are encoded safely instead of producing invalid TOML.
  154. cat > "$outfile" <<HEREDOC
  155. name = "$(toml_escape_string "$name")"
  156. description = "$(toml_escape_string "$description")"
  157. developer_instructions = "$(toml_escape_string "$body")"
  158. HEREDOC
  159. }
  160. convert_gemini_cli() {
  161. local file="$1"
  162. local name description slug outdir outfile body
  163. name="$(get_field "name" "$file")"
  164. description="$(get_field "description" "$file")"
  165. slug="$(slugify "$name")"
  166. body="$(get_body "$file")"
  167. # Gemini CLI subagent format: .md file in ~/.gemini/agents/
  168. outdir="$OUT_DIR/gemini-cli/agents"
  169. outfile="$outdir/${slug}.md"
  170. mkdir -p "$outdir"
  171. cat > "$outfile" <<HEREDOC
  172. ---
  173. name: ${slug}
  174. description: ${description}
  175. ---
  176. ${body}
  177. HEREDOC
  178. }
  179. # Map known color names and normalize to OpenCode-safe #RRGGBB values.
  180. resolve_opencode_color() {
  181. local c="$1"
  182. local mapped
  183. c="$(printf '%s' "$c" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr '[:upper:]' '[:lower:]')"
  184. case "$c" in
  185. cyan) mapped="#00FFFF" ;;
  186. blue) mapped="#3498DB" ;;
  187. green) mapped="#2ECC71" ;;
  188. red) mapped="#E74C3C" ;;
  189. purple) mapped="#9B59B6" ;;
  190. orange) mapped="#F39C12" ;;
  191. teal) mapped="#008080" ;;
  192. indigo) mapped="#6366F1" ;;
  193. pink) mapped="#E84393" ;;
  194. gold) mapped="#EAB308" ;;
  195. amber) mapped="#F59E0B" ;;
  196. neon-green) mapped="#10B981" ;;
  197. neon-cyan) mapped="#06B6D4" ;;
  198. metallic-blue) mapped="#3B82F6" ;;
  199. yellow) mapped="#EAB308" ;;
  200. violet) mapped="#8B5CF6" ;;
  201. rose) mapped="#F43F5E" ;;
  202. lime) mapped="#84CC16" ;;
  203. gray) mapped="#6B7280" ;;
  204. fuchsia) mapped="#D946EF" ;;
  205. *) mapped="$c" ;;
  206. esac
  207. if [[ "$mapped" =~ ^#[0-9a-fA-F]{6}$ ]]; then
  208. printf '#%s\n' "$(printf '%s' "${mapped#\#}" | tr '[:lower:]' '[:upper:]')"
  209. return
  210. fi
  211. if [[ "$mapped" =~ ^[0-9a-fA-F]{6}$ ]]; then
  212. printf '#%s\n' "$(printf '%s' "$mapped" | tr '[:lower:]' '[:upper:]')"
  213. return
  214. fi
  215. printf '#6B7280\n'
  216. }
  217. convert_opencode() {
  218. local file="$1"
  219. local name description color slug outfile body
  220. name="$(get_field "name" "$file")"
  221. description="$(get_field "description" "$file")"
  222. color="$(resolve_opencode_color "$(get_field "color" "$file")")"
  223. slug="$(slugify "$name")"
  224. body="$(get_body "$file")"
  225. outfile="$OUT_DIR/opencode/agents/${slug}.md"
  226. mkdir -p "$OUT_DIR/opencode/agents"
  227. # OpenCode agent format: .md with YAML frontmatter in .opencode/agents/.
  228. # Named colors are resolved to hex via resolve_opencode_color().
  229. cat > "$outfile" <<HEREDOC
  230. ---
  231. name: ${name}
  232. description: ${description}
  233. mode: subagent
  234. color: '${color}'
  235. ---
  236. ${body}
  237. HEREDOC
  238. }
  239. convert_cursor() {
  240. local file="$1"
  241. local name description slug outfile body
  242. name="$(get_field "name" "$file")"
  243. description="$(get_field "description" "$file")"
  244. slug="$(slugify "$name")"
  245. body="$(get_body "$file")"
  246. outfile="$OUT_DIR/cursor/rules/${slug}.mdc"
  247. mkdir -p "$OUT_DIR/cursor/rules"
  248. # Cursor .mdc format: description + globs + alwaysApply frontmatter
  249. cat > "$outfile" <<HEREDOC
  250. ---
  251. description: ${description}
  252. globs: ""
  253. alwaysApply: false
  254. ---
  255. ${body}
  256. HEREDOC
  257. }
  258. convert_openclaw() {
  259. local file="$1"
  260. local name description slug outdir body
  261. local soul_content="" agents_content=""
  262. name="$(get_field "name" "$file")"
  263. description="$(get_field "description" "$file")"
  264. slug="$(slugify "$name")"
  265. body="$(get_body "$file")"
  266. outdir="$OUT_DIR/openclaw/$slug"
  267. mkdir -p "$outdir"
  268. # Split body sections into SOUL.md (persona) vs AGENTS.md (operations)
  269. # by matching ## header keywords. Unmatched sections go to AGENTS.md.
  270. #
  271. # SOUL keywords: identity, learning & memory, communication, style,
  272. # critical rules, rules you must follow
  273. # AGENTS keywords: everything else (mission, deliverables, workflow, etc.)
  274. local current_target="agents" # default bucket
  275. local current_section=""
  276. while IFS= read -r line; do
  277. # Detect ## headers (with or without emoji prefixes)
  278. if [[ "$line" =~ ^##[[:space:]] ]]; then
  279. # Flush previous section
  280. if [[ -n "$current_section" ]]; then
  281. if [[ "$current_target" == "soul" ]]; then
  282. soul_content+="$current_section"
  283. else
  284. agents_content+="$current_section"
  285. fi
  286. fi
  287. current_section=""
  288. # Classify this header by keyword (case-insensitive)
  289. local header_lower
  290. header_lower="$(echo "$line" | tr '[:upper:]' '[:lower:]')"
  291. if [[ "$header_lower" =~ identity ]] ||
  292. [[ "$header_lower" =~ learning.*memory ]] ||
  293. [[ "$header_lower" =~ communication ]] ||
  294. [[ "$header_lower" =~ style ]] ||
  295. [[ "$header_lower" =~ critical.rule ]] ||
  296. [[ "$header_lower" =~ rules.you.must.follow ]]; then
  297. current_target="soul"
  298. else
  299. current_target="agents"
  300. fi
  301. fi
  302. current_section+="$line"$'\n'
  303. done <<< "$body"
  304. # Flush final section
  305. if [[ -n "$current_section" ]]; then
  306. if [[ "$current_target" == "soul" ]]; then
  307. soul_content+="$current_section"
  308. else
  309. agents_content+="$current_section"
  310. fi
  311. fi
  312. # Write SOUL.md — persona, tone, boundaries
  313. cat > "$outdir/SOUL.md" <<HEREDOC
  314. ${soul_content}
  315. HEREDOC
  316. # Write AGENTS.md — mission, deliverables, workflow
  317. cat > "$outdir/AGENTS.md" <<HEREDOC
  318. ${agents_content}
  319. HEREDOC
  320. # Write IDENTITY.md — emoji + name + vibe from frontmatter, fallback to description
  321. local emoji vibe
  322. emoji="$(get_field "emoji" "$file")"
  323. vibe="$(get_field "vibe" "$file")"
  324. if [[ -n "$emoji" && -n "$vibe" ]]; then
  325. cat > "$outdir/IDENTITY.md" <<HEREDOC
  326. # ${emoji} ${name}
  327. ${vibe}
  328. HEREDOC
  329. else
  330. cat > "$outdir/IDENTITY.md" <<HEREDOC
  331. # ${name}
  332. ${description}
  333. HEREDOC
  334. fi
  335. }
  336. convert_qwen() {
  337. local file="$1"
  338. local name description tools slug outfile body
  339. name="$(get_field "name" "$file")"
  340. description="$(get_field "description" "$file")"
  341. tools="$(get_field "tools" "$file")"
  342. slug="$(slugify "$name")"
  343. body="$(get_body "$file")"
  344. outfile="$OUT_DIR/qwen/agents/${slug}.md"
  345. mkdir -p "$(dirname "$outfile")"
  346. # Qwen Code SubAgent format: .md with YAML frontmatter in ~/.qwen/agents/
  347. # name and description required; tools optional (only if present in source)
  348. if [[ -n "$tools" ]]; then
  349. cat > "$outfile" <<HEREDOC
  350. ---
  351. name: ${slug}
  352. description: ${description}
  353. tools: ${tools}
  354. ---
  355. ${body}
  356. HEREDOC
  357. else
  358. cat > "$outfile" <<HEREDOC
  359. ---
  360. name: ${slug}
  361. description: ${description}
  362. ---
  363. ${body}
  364. HEREDOC
  365. fi
  366. }
  367. convert_kimi() {
  368. local file="$1"
  369. local name description slug outdir agent_file body
  370. name="$(get_field "name" "$file")"
  371. description="$(get_field "description" "$file")"
  372. slug="$(slugify "$name")"
  373. body="$(get_body "$file")"
  374. outdir="$OUT_DIR/kimi/$slug"
  375. agent_file="$outdir/agent.yaml"
  376. mkdir -p "$outdir"
  377. # Kimi Code CLI agent format: YAML with separate system prompt file
  378. # Uses extend: default to inherit Kimi's default toolset
  379. cat > "$agent_file" <<HEREDOC
  380. version: 1
  381. agent:
  382. name: ${slug}
  383. extend: default
  384. system_prompt_path: ./system.md
  385. HEREDOC
  386. # Write system prompt to separate file
  387. cat > "$outdir/system.md" <<HEREDOC
  388. # ${name}
  389. ${description}
  390. ${body}
  391. HEREDOC
  392. }
  393. convert_vibe() {
  394. local file="$1"
  395. local name description slug outdir agent_file prompt_file body
  396. name="$(get_field "name" "$file")"
  397. description="$(get_field "description" "$file")"
  398. slug="$(slugify "$name")"
  399. body="$(get_body "$file")"
  400. # Mistral Vibe uses two files per agent:
  401. # 1. A TOML configuration file in ~/.vibe/agents/<slug>.toml
  402. # 2. A markdown prompt file in ~/.vibe/prompts/<slug>.md
  403. outdir="$OUT_DIR/vibe"
  404. agent_file="$outdir/agents/${slug}.toml"
  405. prompt_file="$outdir/prompts/${slug}.md"
  406. mkdir -p "$outdir/agents" "$outdir/prompts"
  407. # Write the TOML agent configuration
  408. cat > "$agent_file" <<HEREDOC
  409. agent_type = "agent"
  410. system_prompt_id = "${slug}"
  411. HEREDOC
  412. # Write the markdown prompt file
  413. cat > "$prompt_file" <<HEREDOC
  414. # ${name}
  415. ${description}
  416. ${body}
  417. HEREDOC
  418. }
  419. # Aider and Windsurf are single-file formats — accumulate into temp files
  420. # then write at the end.
  421. AIDER_TMP="$(mktemp)"
  422. WINDSURF_TMP="$(mktemp)"
  423. trap 'rm -f "$AIDER_TMP" "$WINDSURF_TMP"' EXIT
  424. # Write Aider/Windsurf headers once
  425. cat > "$AIDER_TMP" <<'HEREDOC'
  426. # The Agency — AI Agent Conventions
  427. #
  428. # This file provides Aider with the full roster of specialized AI agents from
  429. # The Agency (https://github.com/msitarzewski/agency-agents).
  430. #
  431. # To activate an agent, reference it by name in your Aider session prompt, e.g.:
  432. # "Use the Frontend Developer agent to review this component."
  433. #
  434. # Generated by scripts/convert.sh — do not edit manually.
  435. HEREDOC
  436. cat > "$WINDSURF_TMP" <<'HEREDOC'
  437. # The Agency — AI Agent Rules for Windsurf
  438. #
  439. # Full roster of specialized AI agents from The Agency.
  440. # To activate an agent, reference it by name in your Windsurf conversation.
  441. #
  442. # Generated by scripts/convert.sh — do not edit manually.
  443. HEREDOC
  444. accumulate_aider() {
  445. local file="$1"
  446. local name description body
  447. name="$(get_field "name" "$file")"
  448. description="$(get_field "description" "$file")"
  449. body="$(get_body "$file")"
  450. cat >> "$AIDER_TMP" <<HEREDOC
  451. ---
  452. ## ${name}
  453. > ${description}
  454. ${body}
  455. HEREDOC
  456. }
  457. accumulate_windsurf() {
  458. local file="$1"
  459. local name description body
  460. name="$(get_field "name" "$file")"
  461. description="$(get_field "description" "$file")"
  462. body="$(get_body "$file")"
  463. cat >> "$WINDSURF_TMP" <<HEREDOC
  464. ================================================================================
  465. ## ${name}
  466. ${description}
  467. ================================================================================
  468. ${body}
  469. HEREDOC
  470. }
  471. # --- Main loop ---
  472. # Remove a tool's previously-generated output before regenerating, so renamed or
  473. # deleted agents don't leave orphan files behind (convert.sh overwrites in place
  474. # but never pruned stale output). Preserves the committed README.md — the only
  475. # tracked file under integrations/<tool>/ for conversion targets.
  476. clean_tool_output() {
  477. local dir="$OUT_DIR/$1"
  478. [[ -d "$dir" ]] || return 0
  479. find "$dir" -mindepth 1 -maxdepth 1 ! -name 'README.md' -exec rm -rf {} +
  480. }
  481. run_conversions() {
  482. local tool="$1"
  483. local count=0
  484. if [[ "$tool" == "hermes" ]]; then
  485. clean_tool_output "$tool"
  486. python3 "$SCRIPT_DIR/build-hermes-plugin.py" --repo-root "$REPO_ROOT" --out "$OUT_DIR/hermes"
  487. return
  488. fi
  489. clean_tool_output "$tool"
  490. for dir in "${AGENT_DIRS[@]}"; do
  491. local dirpath="$REPO_ROOT/$dir"
  492. [[ -d "$dirpath" ]] || continue
  493. while IFS= read -r -d '' file; do
  494. # Skip files without frontmatter (non-agent docs like QUICKSTART.md)
  495. local first_line
  496. first_line="$(head -1 "$file")"
  497. [[ "$first_line" == "---" ]] || continue
  498. local name
  499. name="$(get_field "name" "$file")"
  500. [[ -n "$name" ]] || continue
  501. case "$tool" in
  502. antigravity) convert_antigravity "$file" ;;
  503. codex) convert_codex "$file" ;;
  504. gemini-cli) convert_gemini_cli "$file" ;;
  505. opencode) convert_opencode "$file" ;;
  506. cursor) convert_cursor "$file" ;;
  507. openclaw) convert_openclaw "$file" ;;
  508. qwen) convert_qwen "$file" ;;
  509. kimi) convert_kimi "$file" ;;
  510. osaurus) convert_osaurus "$file" ;;
  511. vibe) convert_vibe "$file" ;;
  512. aider) accumulate_aider "$file" ;;
  513. windsurf) accumulate_windsurf "$file" ;;
  514. esac
  515. (( count++ )) || true
  516. done < <(find "$dirpath" -name "*.md" -type f -print0 | sort -z)
  517. done
  518. echo "$count"
  519. }
  520. # --- Entry point ---
  521. main() {
  522. local tool="all"
  523. local use_parallel=false
  524. local parallel_jobs
  525. parallel_jobs="$(parallel_jobs_default)"
  526. while [[ $# -gt 0 ]]; do
  527. case "$1" in
  528. --tool) tool="${2:?'--tool requires a value'}"; shift 2 ;;
  529. --out) OUT_DIR="${2:?'--out requires a value'}"; shift 2 ;;
  530. --parallel) use_parallel=true; shift ;;
  531. --jobs) parallel_jobs="${2:?'--jobs requires a value'}"; shift 2 ;;
  532. --help|-h) usage ;;
  533. *) error "Unknown option: $1"; usage ;;
  534. esac
  535. done
  536. local valid_tools=("antigravity" "gemini-cli" "opencode" "cursor" "aider" "windsurf" "openclaw" "qwen" "kimi" "codex" "osaurus" "hermes" "vibe" "all")
  537. local valid=false
  538. for t in "${valid_tools[@]}"; do [[ "$t" == "$tool" ]] && valid=true && break; done
  539. if ! $valid; then
  540. error "Unknown tool '$tool'. Valid: ${valid_tools[*]}"
  541. exit 1
  542. fi
  543. header "The Agency -- Converting agents to tool-specific formats"
  544. echo " Repo: $REPO_ROOT"
  545. echo " Output: $OUT_DIR"
  546. echo " Tool: $tool"
  547. echo " Date: $TODAY"
  548. if $use_parallel && [[ "$tool" == "all" ]]; then
  549. info "Parallel mode: output buffered so each tool's output stays together."
  550. fi
  551. local tools_to_run=()
  552. if [[ "$tool" == "all" ]]; then
  553. tools_to_run=("antigravity" "gemini-cli" "opencode" "cursor" "aider" "windsurf" "openclaw" "qwen" "kimi" "codex" "osaurus" "hermes" "vibe")
  554. else
  555. tools_to_run=("$tool")
  556. fi
  557. local total=0
  558. local n_tools=${#tools_to_run[@]}
  559. if $use_parallel && [[ "$tool" == "all" ]]; then
  560. # Tools that write to separate dirs can run in parallel; buffer output so each tool's output stays together
  561. local parallel_tools=(antigravity gemini-cli opencode cursor openclaw qwen codex osaurus hermes vibe)
  562. local parallel_out_dir
  563. parallel_out_dir="$(mktemp -d)"
  564. info "Converting: ${#parallel_tools[@]}/${n_tools} tools in parallel (output buffered per tool)..."
  565. export AGENCY_CONVERT_OUT_DIR="$parallel_out_dir"
  566. export AGENCY_CONVERT_SCRIPT="$SCRIPT_DIR/convert.sh"
  567. export AGENCY_CONVERT_OUT="$OUT_DIR"
  568. 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'
  569. for t in "${parallel_tools[@]}"; do
  570. [[ -f "$parallel_out_dir/$t" ]] && cat "$parallel_out_dir/$t"
  571. done
  572. rm -rf "$parallel_out_dir"
  573. local idx=8
  574. for t in aider windsurf; do
  575. progress_bar "$idx" "$n_tools"
  576. printf "\n"
  577. header "Converting: $t ($idx/$n_tools)"
  578. local count
  579. count="$(run_conversions "$t")"
  580. total=$(( total + count ))
  581. info "Converted $count agents for $t"
  582. (( idx++ )) || true
  583. done
  584. else
  585. local i=0
  586. for t in "${tools_to_run[@]}"; do
  587. (( i++ )) || true
  588. progress_bar "$i" "$n_tools"
  589. printf "\n"
  590. header "Converting: $t ($i/$n_tools)"
  591. local count
  592. count="$(run_conversions "$t")"
  593. total=$(( total + count ))
  594. info "Converted $count agents for $t"
  595. done
  596. fi
  597. # Write single-file outputs after accumulation
  598. if [[ "$tool" == "all" || "$tool" == "aider" ]]; then
  599. mkdir -p "$OUT_DIR/aider"
  600. cp "$AIDER_TMP" "$OUT_DIR/aider/CONVENTIONS.md"
  601. info "Wrote integrations/aider/CONVENTIONS.md"
  602. fi
  603. if [[ "$tool" == "all" || "$tool" == "windsurf" ]]; then
  604. mkdir -p "$OUT_DIR/windsurf"
  605. cp "$WINDSURF_TMP" "$OUT_DIR/windsurf/.windsurfrules"
  606. info "Wrote integrations/windsurf/.windsurfrules"
  607. fi
  608. echo ""
  609. if $use_parallel && [[ "$tool" == "all" ]]; then
  610. info "Done. $n_tools tools (parallel; total conversions not aggregated)."
  611. else
  612. info "Done. Total conversions: $total"
  613. fi
  614. }
  615. main "$@"