diff --git a/scripts/cleanup-claude-dirs.sh b/scripts/cleanup-claude-dirs.sh new file mode 100644 index 0000000..52905d2 --- /dev/null +++ b/scripts/cleanup-claude-dirs.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# cleanup-claude-dirs.sh — Remove .claude/ directories from all repos +# +# .claude/ is local workspace config (MCP settings, worktrees) and should +# never be committed. This script: +# 1. Checks if .claude/ exists in the repo via Gitea API +# 2. Deletes all files in .claude/ via API +# 3. Ensures .claude/ is in .gitignore +# +# Usage: +# cleanup-claude-dirs.sh # all repos +# cleanup-claude-dirs.sh MokoOnyx # one repo +# +set -euo pipefail + +GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}" +GITEA_TOKEN="${GITEA_TOKEN:-$(cat ~/.gitea-token 2>/dev/null || echo "")}" + +if [[ -z "$GITEA_TOKEN" ]]; then + echo "ERROR: GITEA_TOKEN not set" + exit 1 +fi + +FILTER="${1:-}" +CLEANED=0 +SKIPPED=0 + +log() { echo "[$(date '+%H:%M:%S')] $*"; } + +# Get all repos across orgs +get_repos() { + for ORG in MokoConsulting ClarksvilleFurs; do + PAGE=1 + while true; do + REPOS=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_URL}/api/v1/orgs/${ORG}/repos?page=${PAGE}&limit=50" 2>/dev/null) + [[ -z "$REPOS" || "$REPOS" == "[]" ]] && break + + echo "$REPOS" | python3 -c " +import sys, json +for r in json.load(sys.stdin): + if not r.get('archived'): + print(f'{r[\"owner\"][\"login\"]}/{r[\"name\"]}|{r[\"default_branch\"]}') +" 2>/dev/null + + COUNT=$(echo "$REPOS" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) + [[ "$COUNT" -lt 50 ]] && break + PAGE=$((PAGE + 1)) + done + done +} + +while IFS='|' read -r FULL_NAME BRANCH; do + [[ -z "$FULL_NAME" ]] && continue + REPO_NAME="${FULL_NAME#*/}" + + # Filter if specified + if [[ -n "$FILTER" && "$REPO_NAME" != "$FILTER" && "$FULL_NAME" != *"$FILTER"* ]]; then + continue + fi + + # Check if .claude/ exists in the repo + CLAUDE_DIR=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_URL}/api/v1/repos/${FULL_NAME}/contents/.claude?ref=${BRANCH}" 2>/dev/null) + + if [[ -z "$CLAUDE_DIR" || "$CLAUDE_DIR" == "null" ]]; then + SKIPPED=$((SKIPPED + 1)) + continue + fi + + log "Cleaning ${FULL_NAME}..." + + # Get all files in .claude/ + FILES=$(echo "$CLAUDE_DIR" | python3 -c " +import sys, json +data = json.load(sys.stdin) +if isinstance(data, list): + for f in data: + if f.get('type') == 'file': + print(f'{f[\"path\"]}|{f[\"sha\"]}') +elif isinstance(data, dict) and data.get('type') == 'file': + print(f'{data[\"path\"]}|{data[\"sha\"]}') +" 2>/dev/null) + + # Delete each file + while IFS='|' read -r FILE_PATH FILE_SHA; do + [[ -z "$FILE_PATH" ]] && continue + ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${FILE_PATH}', safe='/'))" 2>/dev/null) + + RESULT=$(curl -sf -X DELETE \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "${GITEA_URL}/api/v1/repos/${FULL_NAME}/contents/${ENCODED_PATH}" \ + -d "{\"sha\": \"${FILE_SHA}\", \"message\": \"chore: remove .claude/ from version control [skip ci]\", \"branch\": \"${BRANCH}\"}" \ + -w "%{http_code}" -o /dev/null 2>&1) + + if [[ "$RESULT" == "200" ]]; then + log " Deleted: ${FILE_PATH}" + else + log " FAIL: ${FILE_PATH} (HTTP ${RESULT})" + fi + done <<< "$FILES" + + # Also check for .mcp.json + MCP_JSON=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_URL}/api/v1/repos/${FULL_NAME}/contents/.mcp.json?ref=${BRANCH}" 2>/dev/null) + + if [[ -n "$MCP_JSON" && "$MCP_JSON" != "null" ]]; then + MCP_SHA=$(echo "$MCP_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null) + if [[ -n "$MCP_SHA" ]]; then + RESULT=$(curl -sf -X DELETE \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "${GITEA_URL}/api/v1/repos/${FULL_NAME}/contents/.mcp.json" \ + -d "{\"sha\": \"${MCP_SHA}\", \"message\": \"chore: remove .mcp.json from version control [skip ci]\", \"branch\": \"${BRANCH}\"}" \ + -w "%{http_code}" -o /dev/null 2>&1) + [[ "$RESULT" == "200" ]] && log " Deleted: .mcp.json" + fi + fi + + CLEANED=$((CLEANED + 1)) + +done < <(get_repos) + +log "Done. Cleaned: ${CLEANED}, Skipped: ${SKIPPED}"