OpenWriterControle/plugins/claude-code/scripts/install-context.sh
Darren Hinde 10682353e9
fix(install): resolve non-.md context paths like paths.json (#252)
* fix(install): resolve non-.md context paths like paths.json (closes #251)

resolve_component_path() was appending .md to all slash-containing context
IDs, so context:core/config/paths.json was looked up as paths.json.md and
returned empty, triggering the 'Could not find path' warning on install.

Now tries the .md path first (preserving existing behaviour for all markdown
context files), then falls back to the bare path for non-markdown files.

* fix(shellcheck): use read -ra for word-split array assignment (SC2206)

Replace local arr=($var) with read -ra arr <<< "$var" in
install-context.sh to satisfy ShellCheck SC2206 and unblock CI.
2026-03-11 18:56:58 +00:00

254 lines
6.9 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# install-context.sh — Download OAC context files to .claude/context/
#
# Requirements: bash, git (nothing else to install)
#
# Run from project root:
# bash install-context.sh [--profile=standard] [--force] [--dry-run]
set -euo pipefail
GITHUB_REPO="darrenhinde/OpenAgentsControl"
GITHUB_BRANCH="main"
CONTEXT_SOURCE_PATH=".opencode/context"
# Paths are set in main() after parsing --global flag
PROJECT_ROOT="$(pwd)"
GLOBAL_ROOT="${HOME}/.claude"
# Colors
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
log_info() { echo -e "${BLUE}ℹ${NC} $*"; }
log_success() { echo -e "${GREEN}✓${NC} $*"; }
log_warning() { echo -e "${YELLOW}⚠${NC} $*"; }
log_error() { echo -e "${RED}✗${NC} $*" >&2; }
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Download OAC context files. Requirements: git (nothing else to install)
OPTIONS:
--profile=NAME Profile: essential, standard, extended, all (default: standard)
--global Install to ~/.claude/context/ (all projects share it)
--force Re-download even if already installed
--dry-run Show what would be installed without downloading
--help Show this help
PROFILES:
essential/standard Core context (core + openagents-repo)
extended/all Full context (all categories)
SCOPE:
default (no flag) Installs to .claude/context/ in the current project
--global Installs to ~/.claude/context/ for all projects
EOF
exit 0
}
get_categories() {
case "${1:-standard}" in
essential|standard|core)
echo "core openagents-repo"
;;
extended|all|full)
echo "core openagents-repo development ui content-creation data product learning project project-intelligence"
;;
*)
log_error "Unknown profile: $1"
log_info "Valid profiles: essential, standard, extended, all"
exit 1
;;
esac
}
check_dependencies() {
if ! command -v git >/dev/null 2>&1; then
log_error "git is required but not installed."
log_info "Install: brew install git (Mac) or sudo apt install git (Linux)"
exit 1
fi
}
download_context() {
local categories
read -ra categories <<< "$1"
local temp_dir
temp_dir="$(mktemp -d)"
# shellcheck disable=SC2064
trap "rm -rf '${temp_dir}'" EXIT
log_info "Cloning repository (sparse)..."
git clone --depth 1 --filter=blob:none --sparse \
"https://github.com/${GITHUB_REPO}.git" "${temp_dir}" --quiet 2>&1 | grep -v "^$" || true
# Get commit SHA from the clone (no second clone needed)
COMMIT_SHA=$(git -C "${temp_dir}" rev-parse HEAD)
log_info "Configuring sparse checkout..."
local sparse_paths=""
for cat in "${categories[@]}"; do
sparse_paths="${sparse_paths} ${CONTEXT_SOURCE_PATH}/${cat}"
done
sparse_paths="${sparse_paths} ${CONTEXT_SOURCE_PATH}/navigation.md"
# shellcheck disable=SC2086
git -C "${temp_dir}" sparse-checkout set --skip-checks ${sparse_paths} 2>/dev/null
log_info "Copying context files..."
mkdir -p "${CONTEXT_DIR}"
local source_dir="${temp_dir}/${CONTEXT_SOURCE_PATH}"
if [ ! -d "${source_dir}" ]; then
log_error "Context directory not found in repository"
exit 1
fi
cp -r "${source_dir}/"* "${CONTEXT_DIR}/"
local file_count
file_count=$(find "${CONTEXT_DIR}" -type f | wc -l | tr -d ' ')
log_success "Downloaded ${file_count} files"
}
write_manifest() {
local profile="$1"
local categories
read -ra categories <<< "$2"
local commit="$3"
local timestamp
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
mkdir -p "$(dirname "${MANIFEST_FILE}")"
# Build JSON without jq — use printf
local cats_json="["
local files_json="{"
local first=true
for cat in "${categories[@]}"; do
$first || { cats_json="${cats_json},"; files_json="${files_json},"; }
cats_json="${cats_json}\"${cat}\""
local count=0
[ -d "${CONTEXT_DIR}/${cat}" ] && count=$(find "${CONTEXT_DIR}/${cat}" -type f | wc -l | tr -d ' ')
files_json="${files_json}\"${cat}\": ${count}"
first=false
done
cats_json="${cats_json}]"
files_json="${files_json}}"
printf '{
"version": "1.0.0",
"profile": "%s",
"source": {
"repository": "%s",
"branch": "%s",
"commit": "%s",
"downloaded_at": "%s"
},
"categories": %s,
"files": %s
}\n' \
"${profile}" \
"${GITHUB_REPO}" \
"${GITHUB_BRANCH}" \
"${commit}" \
"${timestamp}" \
"${cats_json}" \
"${files_json}" \
> "${MANIFEST_FILE}"
log_success "Manifest created: ${MANIFEST_FILE}"
}
main() {
local profile="standard"
local global=false
local force=false
local dry_run=false
for arg in "$@"; do
case "${arg}" in
--profile=*) profile="${arg#*=}" ;;
--global) global=true ;;
--force) force=true ;;
--dry-run) dry_run=true ;;
--help|-h) usage ;;
*)
log_error "Unknown option: ${arg}"
echo ""
usage
;;
esac
done
# Set install targets based on scope
local context_dir manifest_file oac_json scope_label
if [ "${global}" = true ]; then
context_dir="${GLOBAL_ROOT}/context"
manifest_file="${GLOBAL_ROOT}/.context-manifest.json"
oac_json="" # global install: no per-project .oac.json
scope_label="global (~/.claude/context)"
else
context_dir="${PROJECT_ROOT}/.claude/context"
manifest_file="${PROJECT_ROOT}/.claude/.context-manifest.json"
oac_json="${PROJECT_ROOT}/.oac.json"
scope_label="project (.claude/context)"
fi
# Export so sub-functions can use them
CONTEXT_DIR="${context_dir}"
MANIFEST_FILE="${manifest_file}"
local categories
categories=$(get_categories "${profile}")
# Already installed?
if [ -f "${MANIFEST_FILE}" ] && [ "${force}" = false ]; then
log_warning "Context already installed at ${scope_label}. Use --force to reinstall."
log_info "Manifest: ${MANIFEST_FILE}"
exit 0
fi
check_dependencies
echo ""
log_info "Scope: ${scope_label}"
log_info "Profile: ${profile}"
log_info "Categories: ${categories}"
log_info "Target: ${CONTEXT_DIR}"
echo ""
if [ "${dry_run}" = true ]; then
log_info "Dry run — no files downloaded"
exit 0
fi
COMMIT_SHA=""
download_context "${categories}"
write_manifest "${profile}" "${categories}" "${COMMIT_SHA}"
# Write .oac.json for project installs so context-scout uses the fast path
if [ "${global}" = false ]; then
if [ ! -f "${oac_json}" ]; then
printf '{\n "version": "1",\n "context": {\n "root": ".claude/context"\n }\n}\n' > "${oac_json}"
log_success ".oac.json created at project root"
else
log_info ".oac.json already exists — skipping"
fi
else
log_info "Global install — no .oac.json needed (discovery chain finds ~/.claude/context automatically)"
fi
echo ""
log_success "Context installation complete!"
log_info "Scope: ${scope_label}"
log_info "Context: ${CONTEXT_DIR}"
log_info "Manifest: ${MANIFEST_FILE}"
echo ""
}
main "$@"