#!/usr/bin/env bash set -euo pipefail script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" repo_root="$(cd -- "$script_dir/.." && pwd -P)" canonical_home="${COPILOT_RESOURCES_HOME:-$HOME/.copilot-resources}" state_dir="${COPILOT_RESOURCES_STATE_DIR:-$HOME/.copilot-resources-state}" copilot_home="${COPILOT_HOME:-$HOME/.copilot}" managed_shell_env="" shell_rc_file="" vscode_settings_file="" usage() { cat <<'EOF' Usage: install/bootstrap.sh [--print-home] Creates a stable canonical home for the repository and links default VS Code and Copilot discovery paths back to the repository. EOF } resolve_dir() { cd -- "$1" && pwd -P } ensure_parent_dir() { mkdir -p -- "$(dirname -- "$1")" } find_node_bin() { if command -v node >/dev/null 2>&1; then command -v node return 0 fi if command -v nodejs >/dev/null 2>&1; then command -v nodejs return 0 fi return 1 } link_path() { local target="$1" local link_path="$2" ensure_parent_dir "$link_path" if [[ -L "$link_path" ]]; then local existing_target existing_target="$(cd -- "$(dirname -- "$link_path")" && resolve_dir "$(readlink "$link_path")")" if [[ "$existing_target" == "$target" ]]; then return 0 fi printf 'Refusing to replace existing symlink %s -> %s\n' "$link_path" "$existing_target" >&2 return 1 fi if [[ -e "$link_path" ]]; then if [[ -d "$link_path" ]] && [[ -z "$(find "$link_path" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]]; then rmdir "$link_path" else printf 'Refusing to replace existing path: %s\n' "$link_path" >&2 return 1 fi fi ln -s -- "$target" "$link_path" } detect_vscode_user_dir() { if [[ -n "${VSCODE_USER_DIR:-}" ]]; then printf '%s\n' "$VSCODE_USER_DIR" return 0 fi case "$(uname -s)" in Darwin) printf '%s\n' "$HOME/Library/Application Support/Code/User" ;; Linux) printf '%s\n' "$HOME/.config/Code/User" ;; *) printf '%s\n' "$HOME/.config/Code/User" ;; esac } detect_shell_rc_file() { case "$(basename -- "${SHELL:-}")" in zsh) printf '%s\n' "$HOME/.zshrc" ;; bash) printf '%s\n' "$HOME/.bashrc" ;; *) printf '%s\n' "$HOME/.profile" ;; esac } upsert_managed_block() { local file_path="$1" local begin_marker="$2" local end_marker="$3" local body="$4" local temp_file temp_file="$(mktemp)" ensure_parent_dir "$file_path" if [[ -f "$file_path" ]]; then awk -v begin_marker="$begin_marker" -v end_marker="$end_marker" ' $0 == begin_marker { skip = 1; next } $0 == end_marker { skip = 0; next } !skip { print } ' "$file_path" > "$temp_file" fi if [[ -s "$temp_file" ]]; then printf '\n' >> "$temp_file" fi cat >> "$temp_file" < "$managed_shell_env" <>> copilot-resources bootstrap >>>" \ "# <<< copilot-resources bootstrap <<<" \ "$shell_block" } merge_vscode_settings() { local node_bin vscode_settings_file="$vscode_user_dir/settings.json" if ! node_bin="$(find_node_bin)"; then printf 'Skipping VS Code settings merge because Node.js is not available.\n' >&2 return 0 fi ensure_parent_dir "$vscode_settings_file" "$node_bin" "$script_dir/merge-vscode-settings.mjs" \ --target "$vscode_settings_file" \ --template "$canonical_home/config/vscode/settings.template.jsonc" \ --set "COPILOT_RESOURCES_HOME=$canonical_home" } write_state() { mkdir -p -- "$state_dir" cat > "$state_dir/install-state.json" <&2 exit 1 fi else ln -s -- "$repo_root" "$canonical_home" fi mkdir -p -- "$copilot_home" vscode_user_dir="$(detect_vscode_user_dir)" mkdir -p -- "$vscode_user_dir" link_path "$canonical_home/resources/skills" "$copilot_home/skills" link_path "$canonical_home/resources/agents" "$copilot_home/agents" link_path "$canonical_home/resources/instructions" "$copilot_home/instructions" link_path "$canonical_home/resources/hooks" "$copilot_home/hooks" link_path "$canonical_home/resources/prompts" "$vscode_user_dir/prompts" write_managed_shell_env merge_vscode_settings write_state cat <