welcome/.gitea/sync_beads.py
rootiest b6124e98a8
All checks were successful
Sync Beads to Gitea Issues / sync-beads (push) Successful in 7s
feat: integrate beads
2026-01-19 21:33:54 -05:00

107 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import os
import requests
import sys
# Configuration from Environment
TOKEN = os.getenv("GITEA_TOKEN")
URL = os.getenv("GITEA_URL")
REPO = os.getenv("REPO_NAME")
HEADERS = {"Authorization": f"token {TOKEN}", "Content-Type": "application/json"}
def sync():
beads_path = ".beads/issues.jsonl"
if not os.path.exists(beads_path):
print(f"❌ ERROR: {beads_path} not found.")
sys.exit(1)
print(f"🔍 Reading Beads from: {beads_path}")
# 1. Fetch existing issues from Gitea
try:
api_url = f"{URL}/api/v1/repos/{REPO}/issues?state=all"
resp = requests.get(api_url, headers=HEADERS)
resp.raise_for_status()
existing = {
i["title"].split("]")[0][1:]: i for i in resp.json() if "]" in i["title"]
}
print(f"📡 Found {len(existing)} existing issues in Gitea.")
except Exception as e:
print(f"❌ Gitea API Connection Failed: {e}")
sys.exit(1)
# 2. Parse the Beads JSONL file
processed_count = 0
skipped_count = 0
with open(beads_path, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
bid = data.get("id")
status = data.get("status")
title = data.get("title")
itype = data.get("issue_type", "unknown")
# --- THE FIX: FILTER TOMBSTONES ---
if status == "tombstone":
print(f"👻 Skipping tombstone: {bid} ({title})")
skipped_count += 1
continue
# ----------------------------------
if not bid:
print("⚠️ Skipping line: No ID found.")
continue
processed_count += 1
# Normalize the state for Gitea
# Beads can use 'closed', 'done', or 'finished'
is_closed = status in ["closed", "done", "finished"]
gitea_state = "closed" if is_closed else "open"
unique_title = f"[{bid}] {title}"
payload = {
"title": unique_title,
"body": f"{data.get('description', 'No description provided.')}\n\n---\n**Beads ID:** `{bid}`\n**Type:** `{itype}`",
"state": gitea_state, # Use the normalized variable here
}
if bid in existing:
print(
f"✅ Updating Gitea Issue #{existing[bid]['number']} for {bid}"
)
requests.patch(
f"{URL}/api/v1/repos/{REPO}/issues/{existing[bid]['number']}",
headers=HEADERS,
json=payload,
)
else:
print(f" Creating New Gitea Issue for {bid}")
r = requests.post(
f"{URL}/api/v1/repos/{REPO}/issues",
headers=HEADERS,
json=payload,
)
r.raise_for_status()
except Exception as e:
print(f"⚠️ Error processing bead: {e}")
print(f"🏁 Finished. Active: {processed_count}, Skipped: {skipped_count}")
# We check if we have ANY beads (active or tombstone) to validate the file isn't broken
if (processed_count + skipped_count) == 0:
print("❌ ERROR: File was found but it was EMPTY.")
sys.exit(1)
if __name__ == "__main__":
sync()