download-context.sh 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. #!/usr/bin/env bash
  2. # download-context.sh - Fetch context files from GitHub repository
  3. # Part of OpenAgents Control (OAC) Claude Code Plugin
  4. set -euo pipefail
  5. # Configuration
  6. GITHUB_REPO="${GITHUB_REPO:-darrenhinde/OpenAgentsControl}"
  7. GITHUB_BRANCH="${GITHUB_BRANCH:-main}"
  8. CONTEXT_DIR="${CLAUDE_PLUGIN_ROOT:-.}/context"
  9. MANIFEST_FILE="${CLAUDE_PLUGIN_ROOT:-.}/.context-manifest.json"
  10. GITHUB_API_BASE="https://api.github.com/repos/${GITHUB_REPO}"
  11. GITHUB_RAW_BASE="https://raw.githubusercontent.com/${GITHUB_REPO}/${GITHUB_BRANCH}"
  12. # Core categories (minimal required set)
  13. CORE_CATEGORIES=(
  14. "core"
  15. "openagents-repo"
  16. )
  17. # All available categories
  18. ALL_CATEGORIES=(
  19. "core"
  20. "openagents-repo"
  21. "development"
  22. "ui"
  23. "content-creation"
  24. "data"
  25. "product"
  26. "learning"
  27. "project"
  28. "project-intelligence"
  29. )
  30. # Colors for output
  31. RED='\033[0;31m'
  32. GREEN='\033[0;32m'
  33. YELLOW='\033[1;33m'
  34. BLUE='\033[0;34m'
  35. NC='\033[0m' # No Color
  36. # Usage information
  37. usage() {
  38. cat <<EOF
  39. Usage: $(basename "$0") [OPTIONS]
  40. Download context files from OpenAgents Control GitHub repository.
  41. OPTIONS:
  42. --core Download core categories only (core, openagents-repo)
  43. --all Download all available categories
  44. --category=NAME Download specific category (can be used multiple times)
  45. --branch=NAME Use specific branch (default: main)
  46. --pr=NUMBER Download from pull request
  47. --force Force re-download even if files exist
  48. --help Show this help message
  49. EXAMPLES:
  50. # Download core context only
  51. $(basename "$0") --core
  52. # Download all context
  53. $(basename "$0") --all
  54. # Download specific categories
  55. $(basename "$0") --category=core --category=development
  56. # Download from a PR
  57. $(basename "$0") --pr=123 --core
  58. CATEGORIES:
  59. Core (required):
  60. - core Universal standards & workflows
  61. - openagents-repo OpenAgents Control repository work
  62. Optional:
  63. - development Software development (all stacks)
  64. - ui Visual design & UX
  65. - content-creation Content creation (all formats)
  66. - data Data engineering & analytics
  67. - product Product management
  68. - learning Educational content
  69. - project Project-specific context
  70. - project-intelligence Project intelligence & decisions
  71. EOF
  72. exit 0
  73. }
  74. # Logging functions
  75. log_info() {
  76. echo -e "${BLUE}ℹ${NC} $*"
  77. }
  78. log_success() {
  79. echo -e "${GREEN}✓${NC} $*"
  80. }
  81. log_warning() {
  82. echo -e "${YELLOW}⚠${NC} $*"
  83. }
  84. log_error() {
  85. echo -e "${RED}✗${NC} $*" >&2
  86. }
  87. # Check if command exists
  88. command_exists() {
  89. command -v "$1" >/dev/null 2>&1
  90. }
  91. # Validate dependencies
  92. check_dependencies() {
  93. local missing=()
  94. if ! command_exists curl; then
  95. missing+=("curl")
  96. fi
  97. if ! command_exists jq; then
  98. missing+=("jq")
  99. fi
  100. if [ ${#missing[@]} -gt 0 ]; then
  101. log_error "Missing required dependencies: ${missing[*]}"
  102. log_info "Install with: brew install ${missing[*]}"
  103. exit 1
  104. fi
  105. }
  106. # Get GitHub authentication token
  107. get_github_token() {
  108. if [ -n "${GITHUB_TOKEN:-}" ]; then
  109. echo "$GITHUB_TOKEN"
  110. elif [ -n "${GH_TOKEN:-}" ]; then
  111. echo "$GH_TOKEN"
  112. elif command_exists gh; then
  113. gh auth token 2>/dev/null || echo ""
  114. else
  115. echo ""
  116. fi
  117. }
  118. # Fetch file from GitHub
  119. fetch_file() {
  120. local file_path="$1"
  121. local output_path="$2"
  122. local token
  123. token=$(get_github_token)
  124. local url="${GITHUB_RAW_BASE}/.opencode/context/${file_path}"
  125. # Create directory if needed
  126. mkdir -p "$(dirname "$output_path")"
  127. # Download file
  128. if [ -n "$token" ]; then
  129. curl -sf -H "Authorization: token $token" "$url" -o "$output_path"
  130. else
  131. curl -sf "$url" -o "$output_path"
  132. fi
  133. }
  134. # List files in a directory from GitHub
  135. list_github_directory() {
  136. local dir_path="$1"
  137. local token
  138. token=$(get_github_token)
  139. local api_url="${GITHUB_API_BASE}/contents/.opencode/context/${dir_path}?ref=${GITHUB_BRANCH}"
  140. if [ -n "$token" ]; then
  141. curl -sf -H "Authorization: token $token" "$api_url" | jq -r '.[] | select(.type == "file") | .path' | sed 's|^.opencode/context/||'
  142. else
  143. curl -sf "$api_url" | jq -r '.[] | select(.type == "file") | .path' | sed 's|^.opencode/context/||'
  144. fi
  145. }
  146. # Recursively download directory
  147. download_directory() {
  148. local category="$1"
  149. local token
  150. token=$(get_github_token)
  151. log_info "Downloading category: $category"
  152. # Get all files in the category recursively
  153. local api_url="${GITHUB_API_BASE}/git/trees/${GITHUB_BRANCH}?recursive=1"
  154. local files
  155. if [ -n "$token" ]; then
  156. files=$(curl -sf -H "Authorization: token $token" "$api_url" | \
  157. jq -r ".tree[] | select(.type == \"blob\" and (.path | startswith(\".opencode/context/${category}/\"))) | .path" | \
  158. sed 's|^.opencode/context/||')
  159. else
  160. files=$(curl -sf "$api_url" | \
  161. jq -r ".tree[] | select(.type == \"blob\" and (.path | startswith(\".opencode/context/${category}/\"))) | .path" | \
  162. sed 's|^.opencode/context/||')
  163. fi
  164. local count=0
  165. local total
  166. total=$(echo "$files" | wc -l | tr -d ' ')
  167. while IFS= read -r file; do
  168. if [ -n "$file" ]; then
  169. ((count++))
  170. local output_path="${CONTEXT_DIR}/${file}"
  171. # Show progress
  172. printf "\r [%d/%d] %s" "$count" "$total" "$file"
  173. if fetch_file "$file" "$output_path" 2>/dev/null; then
  174. : # Success, continue
  175. else
  176. log_warning "Failed to download: $file"
  177. fi
  178. fi
  179. done <<< "$files"
  180. echo "" # New line after progress
  181. log_success "Downloaded $count files from $category"
  182. }
  183. # Verify navigation.md files exist
  184. verify_navigation() {
  185. local categories=("$@")
  186. local missing=()
  187. log_info "Verifying navigation files..."
  188. for category in "${categories[@]}"; do
  189. local nav_file="${CONTEXT_DIR}/${category}/navigation.md"
  190. if [ ! -f "$nav_file" ]; then
  191. missing+=("$category/navigation.md")
  192. fi
  193. done
  194. if [ ${#missing[@]} -gt 0 ]; then
  195. log_warning "Missing navigation files:"
  196. for file in "${missing[@]}"; do
  197. echo " - $file"
  198. done
  199. return 1
  200. else
  201. log_success "All navigation files present"
  202. return 0
  203. fi
  204. }
  205. # Create context manifest
  206. create_manifest() {
  207. local categories=("$@")
  208. log_info "Creating context manifest..."
  209. # Get commit SHA
  210. local token
  211. token=$(get_github_token)
  212. local commit_sha
  213. if [ -n "$token" ]; then
  214. commit_sha=$(curl -sf -H "Authorization: token $token" \
  215. "${GITHUB_API_BASE}/commits/${GITHUB_BRANCH}" | jq -r '.sha')
  216. else
  217. commit_sha=$(curl -sf "${GITHUB_API_BASE}/commits/${GITHUB_BRANCH}" | jq -r '.sha')
  218. fi
  219. # Create manifest JSON
  220. cat > "$MANIFEST_FILE" <<EOF
  221. {
  222. "version": "1.0.0",
  223. "source": {
  224. "repository": "${GITHUB_REPO}",
  225. "branch": "${GITHUB_BRANCH}",
  226. "commit": "${commit_sha}",
  227. "downloaded_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  228. },
  229. "categories": $(printf '%s\n' "${categories[@]}" | jq -R . | jq -s .),
  230. "files": {
  231. $(
  232. for category in "${categories[@]}"; do
  233. local count
  234. count=$(find "${CONTEXT_DIR}/${category}" -type f 2>/dev/null | wc -l | tr -d ' ')
  235. echo " \"${category}\": ${count},"
  236. done | sed '$ s/,$//'
  237. )
  238. }
  239. }
  240. EOF
  241. log_success "Created manifest: $MANIFEST_FILE"
  242. }
  243. # Main download function
  244. download_context() {
  245. local categories=("$@")
  246. log_info "Starting context download..."
  247. log_info "Repository: ${GITHUB_REPO}"
  248. log_info "Branch: ${GITHUB_BRANCH}"
  249. log_info "Categories: ${categories[*]}"
  250. echo ""
  251. # Create context directory
  252. mkdir -p "$CONTEXT_DIR"
  253. # Download each category
  254. for category in "${categories[@]}"; do
  255. download_directory "$category"
  256. done
  257. echo ""
  258. # Verify navigation files
  259. verify_navigation "${categories[@]}"
  260. # Create manifest
  261. create_manifest "${categories[@]}"
  262. echo ""
  263. log_success "Context download complete!"
  264. log_info "Context location: $CONTEXT_DIR"
  265. log_info "Manifest: $MANIFEST_FILE"
  266. }
  267. # Parse command line arguments
  268. main() {
  269. local categories=()
  270. local download_mode=""
  271. local force=false
  272. # Parse arguments
  273. while [[ $# -gt 0 ]]; do
  274. case $1 in
  275. --core)
  276. download_mode="core"
  277. shift
  278. ;;
  279. --all)
  280. download_mode="all"
  281. shift
  282. ;;
  283. --category=*)
  284. categories+=("${1#*=}")
  285. shift
  286. ;;
  287. --branch=*)
  288. GITHUB_BRANCH="${1#*=}"
  289. shift
  290. ;;
  291. --pr=*)
  292. local pr_number="${1#*=}"
  293. # Get PR branch
  294. local token
  295. token=$(get_github_token)
  296. if [ -n "$token" ]; then
  297. GITHUB_BRANCH=$(curl -sf -H "Authorization: token $token" \
  298. "${GITHUB_API_BASE}/pulls/${pr_number}" | jq -r '.head.ref')
  299. else
  300. GITHUB_BRANCH=$(curl -sf "${GITHUB_API_BASE}/pulls/${pr_number}" | jq -r '.head.ref')
  301. fi
  302. log_info "Using PR #${pr_number} branch: ${GITHUB_BRANCH}"
  303. shift
  304. ;;
  305. --force)
  306. force=true
  307. shift
  308. ;;
  309. --help|-h)
  310. usage
  311. ;;
  312. *)
  313. log_error "Unknown option: $1"
  314. echo ""
  315. usage
  316. ;;
  317. esac
  318. done
  319. # Determine categories to download
  320. if [ "$download_mode" = "core" ]; then
  321. categories=("${CORE_CATEGORIES[@]}")
  322. elif [ "$download_mode" = "all" ]; then
  323. categories=("${ALL_CATEGORIES[@]}")
  324. elif [ ${#categories[@]} -eq 0 ]; then
  325. # Default to core if nothing specified
  326. log_warning "No categories specified, defaulting to --core"
  327. categories=("${CORE_CATEGORIES[@]}")
  328. fi
  329. # Check dependencies
  330. check_dependencies
  331. # Check if context already exists
  332. if [ -f "$MANIFEST_FILE" ] && [ "$force" = false ]; then
  333. log_warning "Context already exists. Use --force to re-download."
  334. log_info "Current manifest:"
  335. cat "$MANIFEST_FILE" | jq .
  336. exit 0
  337. fi
  338. # Download context
  339. download_context "${categories[@]}"
  340. }
  341. # Run main function
  342. main "$@"