feat: add cleanup script to remove .claude/ and .mcp.json from repos
Scans all repos via Gitea API and deletes .claude/ directories and .mcp.json files that were accidentally committed. These are local workspace configs and should be gitignored. Authored-by: Moko Consulting
This commit is contained in:
@@ -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}"
|
||||
Reference in New Issue
Block a user