| 1 | #!/bin/bash |
| 2 | # sync-mesh.sh — Materialize remote squad state locally |
| 3 | # |
| 4 | # Reads mesh.json, fetches remote squads into local directories. |
| 5 | # Run before agent reads. No daemon. No service. ~40 lines. |
| 6 | # |
| 7 | # Usage: ./sync-mesh.sh [path-to-mesh.json] |
| 8 | # ./sync-mesh.sh --init [path-to-mesh.json] |
| 9 | # Requires: jq (https://github.com/jqlang/jq), git, curl |
| 10 | |
| 11 | set -euo pipefail |
| 12 | |
| 13 | # Handle --init mode |
| 14 | if [ "${1:-}" = "--init" ]; then |
| 15 | MESH_JSON="${2:-mesh.json}" |
| 16 | |
| 17 | if [ ! -f "$MESH_JSON" ]; then |
| 18 | echo "❌ $MESH_JSON not found" |
| 19 | exit 1 |
| 20 | fi |
| 21 | |
| 22 | echo "🚀 Initializing mesh state repository..." |
| 23 | squads=$(jq -r '.squads | keys[]' "$MESH_JSON") |
| 24 | |
| 25 | # Create squad directories with placeholder SUMMARY.md |
| 26 | for squad in $squads; do |
| 27 | if [ ! -d "$squad" ]; then |
| 28 | mkdir -p "$squad" |
| 29 | echo " ✓ Created $squad/" |
| 30 | else |
| 31 | echo " • $squad/ exists (skipped)" |
| 32 | fi |
| 33 | |
| 34 | if [ ! -f "$squad/SUMMARY.md" ]; then |
| 35 | echo -e "# $squad\n\n_No state published yet._" > "$squad/SUMMARY.md" |
| 36 | echo " ✓ Created $squad/SUMMARY.md" |
| 37 | else |
| 38 | echo " • $squad/SUMMARY.md exists (skipped)" |
| 39 | fi |
| 40 | done |
| 41 | |
| 42 | # Generate root README.md |
| 43 | if [ ! -f "README.md" ]; then |
| 44 | { |
| 45 | echo "# Squad Mesh State Repository" |
| 46 | echo "" |
| 47 | echo "This repository tracks published state from participating squads." |
| 48 | echo "" |
| 49 | echo "## Participating Squads" |
| 50 | echo "" |
| 51 | for squad in $squads; do |
| 52 | zone=$(jq -r ".squads.\"$squad\".zone" "$MESH_JSON") |
| 53 | echo "- **$squad** (Zone: $zone)" |
| 54 | done |
| 55 | echo "" |
| 56 | echo "Each squad directory contains a \`SUMMARY.md\` with their latest published state." |
| 57 | echo "State is synchronized using \`sync-mesh.sh\` or \`sync-mesh.ps1\`." |
| 58 | } > README.md |
| 59 | echo " ✓ Created README.md" |
| 60 | else |
| 61 | echo " • README.md exists (skipped)" |
| 62 | fi |
| 63 | |
| 64 | echo "" |
| 65 | echo "✅ Mesh state repository initialized" |
| 66 | exit 0 |
| 67 | fi |
| 68 | |
| 69 | MESH_JSON="${1:-mesh.json}" |
| 70 | |
| 71 | # Zone 2: Remote-trusted — git clone/pull |
| 72 | for squad in $(jq -r '.squads | to_entries[] | select(.value.zone == "remote-trusted") | .key' "$MESH_JSON"); do |
| 73 | source=$(jq -r ".squads.\"$squad\".source" "$MESH_JSON") |
| 74 | ref=$(jq -r ".squads.\"$squad\".ref // \"main\"" "$MESH_JSON") |
| 75 | target=$(jq -r ".squads.\"$squad\".sync_to" "$MESH_JSON") |
| 76 | |
| 77 | if [ -d "$target/.git" ]; then |
| 78 | git -C "$target" pull --rebase --quiet 2>/dev/null \ |
| 79 | || echo "⚠ $squad: pull failed (using stale)" |
| 80 | else |
| 81 | mkdir -p "$(dirname "$target")" |
| 82 | git clone --quiet --depth 1 --branch "$ref" "$source" "$target" 2>/dev/null \ |
| 83 | || echo "⚠ $squad: clone failed (unavailable)" |
| 84 | fi |
| 85 | done |
| 86 | |
| 87 | # Zone 3: Remote-opaque — fetch published contracts |
| 88 | for squad in $(jq -r '.squads | to_entries[] | select(.value.zone == "remote-opaque") | .key' "$MESH_JSON"); do |
| 89 | source=$(jq -r ".squads.\"$squad\".source" "$MESH_JSON") |
| 90 | target=$(jq -r ".squads.\"$squad\".sync_to" "$MESH_JSON") |
| 91 | auth=$(jq -r ".squads.\"$squad\".auth // \"\"" "$MESH_JSON") |
| 92 | |
| 93 | mkdir -p "$target" |
| 94 | auth_flag="" |
| 95 | if [ "$auth" = "bearer" ]; then |
| 96 | token_var="$(echo "${squad}" | tr '[:lower:]-' '[:upper:]_')_TOKEN" |
| 97 | [ -n "${!token_var:-}" ] && auth_flag="--header \"Authorization: Bearer ${!token_var}\"" |
| 98 | fi |
| 99 | |
| 100 | eval curl --silent --fail $auth_flag "$source" -o "$target/SUMMARY.md" 2>/dev/null \ |
| 101 | || echo "# ${squad} — unavailable ($(date))" > "$target/SUMMARY.md" |
| 102 | done |
| 103 | |
| 104 | echo "✓ Mesh sync complete" |