#!/usr/bin/env bash # register_project.sh — register a project/repo with the Custodian State Hub # # Usage: scripts/register_project.sh [--additional] # domain: slug of an active domain (e.g. custodian, railiance) # project_path: absolute path to the project directory # --additional: add a second repo to an existing domain; skip CLAUDE.md # # Example: # scripts/register_project.sh railiance /home/worsch/railiance # scripts/register_project.sh railiance /home/worsch/railiance-infra --additional # # What it does: # 1. Verify the API is reachable # 2. Verify the domain exists via GET /domains/{slug}/ # 3. Look up the topic ID for the domain (first active topic) # 4. Check that state-hub is in ~/.claude.json; warn if missing # 5. Write $project_path/CLAUDE.md from the template (skip if exists or --additional) # 6. POST to /repos/ to register the repo # 7. POST a progress event recording the registration set -euo pipefail DOMAIN="${1:-}" PROJECT_PATH="${2:-}" ADDITIONAL="${3:-}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" STATE_HUB_DIR="$(dirname "$SCRIPT_DIR")" API_BASE="${API_BASE:-http://127.0.0.1:8000}" # ── Validate args ────────────────────────────────────────────────────────────── if [[ -z "$DOMAIN" || -z "$PROJECT_PATH" ]]; then echo "Usage: $0 [--additional]" echo " domain: slug of an active domain in the State Hub" echo " project_path: absolute path to project directory" echo " --additional: register a second repo; skip CLAUDE.md generation" exit 1 fi if [[ ! -d "$PROJECT_PATH" ]]; then echo "ERROR: project_path does not exist: $PROJECT_PATH" exit 1 fi PROJECT_NAME="$(basename "$PROJECT_PATH")" REPO_SLUG="$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-\|-$//g')" # ── Step 1: API health check ─────────────────────────────────────────────────── echo "==> Checking API at $API_BASE ..." if ! curl -sf "$API_BASE/state/health" > /dev/null; then echo "ERROR: State Hub API is not reachable." echo " Start it: cd $STATE_HUB_DIR && make api" echo " (requires postgres: make db first)" exit 1 fi echo " API OK" # ── Step 2: Verify domain exists ─────────────────────────────────────────────── echo "==> Verifying domain '$DOMAIN' ..." DOMAIN_JSON="$(curl -sf "$API_BASE/domains/$DOMAIN/" 2>/dev/null || echo 'NOT_FOUND')" if [[ "$DOMAIN_JSON" == "NOT_FOUND" ]] || echo "$DOMAIN_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if d.get('slug') else 1)" 2>/dev/null; then if [[ "$DOMAIN_JSON" == "NOT_FOUND" ]] || ! echo "$DOMAIN_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if d.get('slug') else 1)" 2>/dev/null; then echo "ERROR: Domain '$DOMAIN' not found in the State Hub." echo " To create: make add-domain DOMAIN=$DOMAIN NAME=\"\"" echo " To list available: curl -s $API_BASE/domains/ | python3 -m json.tool" exit 1 fi fi echo " Domain OK" # ── Step 3: Look up topic ID ─────────────────────────────────────────────────── echo "==> Looking up topic for domain '$DOMAIN' ..." TOPICS_JSON="$(curl -sf "$API_BASE/topics/?status=active")" TOPIC_ID="$(echo "$TOPICS_JSON" | python3 -c " import json, sys topics = json.load(sys.stdin) match = next((t for t in topics if t.get('domain_slug') == sys.argv[1]), None) if not match: print('NOT_FOUND') else: print(match['id']) " "$DOMAIN")" if [[ "$TOPIC_ID" == "NOT_FOUND" ]]; then echo "WARNING: No active topic found for domain '$DOMAIN'. CLAUDE.md will omit topic_id." TOPIC_ID="" else echo " topic_id: $TOPIC_ID" fi # ── Step 4: Check MCP registration ──────────────────────────────────────────── echo "==> Checking MCP server registration ..." MCP_OK="$(python3 -c " import json from pathlib import Path f = Path.home() / '.claude.json' if not f.exists(): print('MISSING_FILE') else: d = json.loads(f.read_text()) servers = d.get('mcpServers', {}) print('OK' if 'state-hub' in servers else 'NOT_REGISTERED') ")" if [[ "$MCP_OK" == "MISSING_FILE" ]]; then echo "WARNING: ~/.claude.json not found. MCP server is not registered." elif [[ "$MCP_OK" == "NOT_REGISTERED" ]]; then echo "WARNING: 'state-hub' not found in ~/.claude.json." echo " To register, see CLAUDE.md MCP Server Registration section." else echo " MCP OK" fi # ── Step 5: Write CLAUDE.md ──────────────────────────────────────────────────── CLAUDE_MD="$PROJECT_PATH/CLAUDE.md" TEMPLATE="$SCRIPT_DIR/project_claude_md.template" if [[ "$ADDITIONAL" == "--additional" ]]; then echo "==> --additional flag: skipping CLAUDE.md (already exists for this domain)." elif [[ -f "$CLAUDE_MD" ]]; then echo "==> CLAUDE.md already exists at $CLAUDE_MD — skipping." else echo "==> Writing CLAUDE.md to $CLAUDE_MD ..." sed \ -e "s|{PROJECT_NAME}|$PROJECT_NAME|g" \ -e "s|{DOMAIN}|$DOMAIN|g" \ -e "s|{TOPIC_ID}|$TOPIC_ID|g" \ "$TEMPLATE" > "$CLAUDE_MD" echo " Written." fi # ── Step 6: Register repo in State Hub ──────────────────────────────────────── echo "==> Registering repo '$PROJECT_NAME' under domain '$DOMAIN' ..." REPO_PAYLOAD="$(python3 -c " import json payload = { 'domain_slug': '$DOMAIN', 'slug': '$REPO_SLUG', 'name': '$PROJECT_NAME', 'local_path': '$PROJECT_PATH', } print(json.dumps(payload)) ")" REPO_RESULT="$(curl -sf -X POST "$API_BASE/repos/" \ -H "Content-Type: application/json" \ -d "$REPO_PAYLOAD" 2>/dev/null || echo 'REPO_EXISTS')" if [[ "$REPO_RESULT" == "REPO_EXISTS" ]]; then echo " Repo '$REPO_SLUG' already registered (or slug conflict) — skipping." else echo " Repo registered: $REPO_SLUG" fi # ── Step 7: Record progress event ───────────────────────────────────────────── echo "==> Recording registration event ..." EVENT_JSON="$(python3 -c " import json payload = { $([ -n '$TOPIC_ID' ] && echo "'topic_id': '$TOPIC_ID',") 'event_type': 'milestone', 'summary': 'Project registered with State Hub: $PROJECT_NAME ($DOMAIN)', 'author': 'custodian', 'detail': { 'project_path': '$PROJECT_PATH', 'claude_md': '$CLAUDE_MD', 'domain': '$DOMAIN', 'repo_slug': '$REPO_SLUG', }, } print(json.dumps(payload)) ")" curl -sf -X POST "$API_BASE/progress/" \ -H "Content-Type: application/json" \ -d "$EVENT_JSON" > /dev/null echo " Event recorded." echo "" echo "Registration complete!" echo " Project: $PROJECT_NAME" echo " Domain: $DOMAIN" echo " Repo slug: $REPO_SLUG" [[ -n "$TOPIC_ID" ]] && echo " Topic ID: $TOPIC_ID" echo " CLAUDE.md: $CLAUDE_MD" echo "" echo "Next: restart Claude Code for the MCP server to be available in this project."