install-context.sh 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env bash
  2. # install-context.sh — Download OAC context files to .claude/context/
  3. #
  4. # Requirements: bash, git (nothing else to install)
  5. #
  6. # Run from project root:
  7. # bash install-context.sh [--profile=standard] [--force] [--dry-run]
  8. set -euo pipefail
  9. GITHUB_REPO="darrenhinde/OpenAgentsControl"
  10. GITHUB_BRANCH="main"
  11. CONTEXT_SOURCE_PATH=".opencode/context"
  12. # Paths are set in main() after parsing --global flag
  13. PROJECT_ROOT="$(pwd)"
  14. GLOBAL_ROOT="${HOME}/.claude"
  15. # Colors
  16. RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
  17. log_info() { echo -e "${BLUE}ℹ${NC} $*"; }
  18. log_success() { echo -e "${GREEN}✓${NC} $*"; }
  19. log_warning() { echo -e "${YELLOW}⚠${NC} $*"; }
  20. log_error() { echo -e "${RED}✗${NC} $*" >&2; }
  21. usage() {
  22. cat <<EOF
  23. Usage: $(basename "$0") [OPTIONS]
  24. Download OAC context files. Requirements: git (nothing else to install)
  25. OPTIONS:
  26. --profile=NAME Profile: essential, standard, extended, all (default: standard)
  27. --global Install to ~/.claude/context/ (all projects share it)
  28. --force Re-download even if already installed
  29. --dry-run Show what would be installed without downloading
  30. --help Show this help
  31. PROFILES:
  32. essential/standard Core context (core + openagents-repo)
  33. extended/all Full context (all categories)
  34. SCOPE:
  35. default (no flag) Installs to .claude/context/ in the current project
  36. --global Installs to ~/.claude/context/ for all projects
  37. EOF
  38. exit 0
  39. }
  40. get_categories() {
  41. case "${1:-standard}" in
  42. essential|standard|core)
  43. echo "core openagents-repo"
  44. ;;
  45. extended|all|full)
  46. echo "core openagents-repo development ui content-creation data product learning project project-intelligence"
  47. ;;
  48. *)
  49. log_error "Unknown profile: $1"
  50. log_info "Valid profiles: essential, standard, extended, all"
  51. exit 1
  52. ;;
  53. esac
  54. }
  55. check_dependencies() {
  56. if ! command -v git >/dev/null 2>&1; then
  57. log_error "git is required but not installed."
  58. log_info "Install: brew install git (Mac) or sudo apt install git (Linux)"
  59. exit 1
  60. fi
  61. }
  62. download_context() {
  63. local categories=($1)
  64. local temp_dir
  65. temp_dir="$(mktemp -d)"
  66. # shellcheck disable=SC2064
  67. trap "rm -rf '${temp_dir}'" EXIT
  68. log_info "Cloning repository (sparse)..."
  69. git clone --depth 1 --filter=blob:none --sparse \
  70. "https://github.com/${GITHUB_REPO}.git" "${temp_dir}" --quiet 2>&1 | grep -v "^$" || true
  71. # Get commit SHA from the clone (no second clone needed)
  72. COMMIT_SHA=$(git -C "${temp_dir}" rev-parse HEAD)
  73. log_info "Configuring sparse checkout..."
  74. local sparse_paths=""
  75. for cat in "${categories[@]}"; do
  76. sparse_paths="${sparse_paths} ${CONTEXT_SOURCE_PATH}/${cat}"
  77. done
  78. sparse_paths="${sparse_paths} ${CONTEXT_SOURCE_PATH}/navigation.md"
  79. # shellcheck disable=SC2086
  80. git -C "${temp_dir}" sparse-checkout set --skip-checks ${sparse_paths} 2>/dev/null
  81. log_info "Copying context files..."
  82. mkdir -p "${CONTEXT_DIR}"
  83. local source_dir="${temp_dir}/${CONTEXT_SOURCE_PATH}"
  84. if [ ! -d "${source_dir}" ]; then
  85. log_error "Context directory not found in repository"
  86. exit 1
  87. fi
  88. cp -r "${source_dir}/"* "${CONTEXT_DIR}/"
  89. local file_count
  90. file_count=$(find "${CONTEXT_DIR}" -type f | wc -l | tr -d ' ')
  91. log_success "Downloaded ${file_count} files"
  92. }
  93. write_manifest() {
  94. local profile="$1"
  95. local categories=($2)
  96. local commit="$3"
  97. local timestamp
  98. timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  99. mkdir -p "$(dirname "${MANIFEST_FILE}")"
  100. # Build JSON without jq — use printf
  101. local cats_json="["
  102. local files_json="{"
  103. local first=true
  104. for cat in "${categories[@]}"; do
  105. $first || { cats_json="${cats_json},"; files_json="${files_json},"; }
  106. cats_json="${cats_json}\"${cat}\""
  107. local count=0
  108. [ -d "${CONTEXT_DIR}/${cat}" ] && count=$(find "${CONTEXT_DIR}/${cat}" -type f | wc -l | tr -d ' ')
  109. files_json="${files_json}\"${cat}\": ${count}"
  110. first=false
  111. done
  112. cats_json="${cats_json}]"
  113. files_json="${files_json}}"
  114. printf '{
  115. "version": "1.0.0",
  116. "profile": "%s",
  117. "source": {
  118. "repository": "%s",
  119. "branch": "%s",
  120. "commit": "%s",
  121. "downloaded_at": "%s"
  122. },
  123. "categories": %s,
  124. "files": %s
  125. }\n' \
  126. "${profile}" \
  127. "${GITHUB_REPO}" \
  128. "${GITHUB_BRANCH}" \
  129. "${commit}" \
  130. "${timestamp}" \
  131. "${cats_json}" \
  132. "${files_json}" \
  133. > "${MANIFEST_FILE}"
  134. log_success "Manifest created: ${MANIFEST_FILE}"
  135. }
  136. main() {
  137. local profile="standard"
  138. local global=false
  139. local force=false
  140. local dry_run=false
  141. for arg in "$@"; do
  142. case "${arg}" in
  143. --profile=*) profile="${arg#*=}" ;;
  144. --global) global=true ;;
  145. --force) force=true ;;
  146. --dry-run) dry_run=true ;;
  147. --help|-h) usage ;;
  148. *)
  149. log_error "Unknown option: ${arg}"
  150. echo ""
  151. usage
  152. ;;
  153. esac
  154. done
  155. # Set install targets based on scope
  156. local context_dir manifest_file oac_json scope_label
  157. if [ "${global}" = true ]; then
  158. context_dir="${GLOBAL_ROOT}/context"
  159. manifest_file="${GLOBAL_ROOT}/.context-manifest.json"
  160. oac_json="" # global install: no per-project .oac.json
  161. scope_label="global (~/.claude/context)"
  162. else
  163. context_dir="${PROJECT_ROOT}/.claude/context"
  164. manifest_file="${PROJECT_ROOT}/.claude/.context-manifest.json"
  165. oac_json="${PROJECT_ROOT}/.oac.json"
  166. scope_label="project (.claude/context)"
  167. fi
  168. # Export so sub-functions can use them
  169. CONTEXT_DIR="${context_dir}"
  170. MANIFEST_FILE="${manifest_file}"
  171. local categories
  172. categories=$(get_categories "${profile}")
  173. # Already installed?
  174. if [ -f "${MANIFEST_FILE}" ] && [ "${force}" = false ]; then
  175. log_warning "Context already installed at ${scope_label}. Use --force to reinstall."
  176. log_info "Manifest: ${MANIFEST_FILE}"
  177. exit 0
  178. fi
  179. check_dependencies
  180. echo ""
  181. log_info "Scope: ${scope_label}"
  182. log_info "Profile: ${profile}"
  183. log_info "Categories: ${categories}"
  184. log_info "Target: ${CONTEXT_DIR}"
  185. echo ""
  186. if [ "${dry_run}" = true ]; then
  187. log_info "Dry run — no files downloaded"
  188. exit 0
  189. fi
  190. COMMIT_SHA=""
  191. download_context "${categories}"
  192. write_manifest "${profile}" "${categories}" "${COMMIT_SHA}"
  193. # Write .oac.json for project installs so context-scout uses the fast path
  194. if [ "${global}" = false ]; then
  195. if [ ! -f "${oac_json}" ]; then
  196. printf '{\n "version": "1",\n "context": {\n "root": ".claude/context"\n }\n}\n' > "${oac_json}"
  197. log_success ".oac.json created at project root"
  198. else
  199. log_info ".oac.json already exists — skipping"
  200. fi
  201. else
  202. log_info "Global install — no .oac.json needed (discovery chain finds ~/.claude/context automatically)"
  203. fi
  204. echo ""
  205. log_success "Context installation complete!"
  206. log_info "Scope: ${scope_label}"
  207. log_info "Context: ${CONTEXT_DIR}"
  208. log_info "Manifest: ${MANIFEST_FILE}"
  209. echo ""
  210. }
  211. main "$@"