From 3623dca77b96c94d21584f5d8638300521247eaa Mon Sep 17 00:00:00 2001 From: Spencer McGuire Date: Sun, 18 Oct 2026 20:50:34 -0600 Subject: [PATCH] scripts: gitea_issues.py drives the tea CLI; 38 decision issues opened on Gitea Co-Authored-By: Claude Fable 5.1 --- scripts/gitea_issues.py | 84 ++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/scripts/gitea_issues.py b/scripts/gitea_issues.py index 3bc2372..91556e2 100644 --- a/scripts/gitea_issues.py +++ b/scripts/gitea_issues.py @@ -1,4 +1,4 @@ -# ruff: noqa: E501 S310 +# ruff: noqa: E501 """Create one Gitea issue per open decision in docs/PLAN.md. Usage (token from a file, never from the command line or chat): @@ -12,13 +12,11 @@ from __future__ import annotations import argparse import json +import subprocess import sys -import urllib.error -import urllib.parse -import urllib.request +import tempfile from pathlib import Path -BASE = "https://gt.pescoinc.biz/api/v1" REPO = "spencerm/PacketStation" PLAN = "https://gt.pescoinc.biz/spencerm/PacketStation/src/branch/main/docs/PLAN.md" @@ -293,22 +291,20 @@ ISSUES: list[tuple[str, str, list[str], str]] = [ ] -def api(method: str, path: str, token: str, body: dict | None = None): - data = json.dumps(body).encode() if body is not None else None - req = urllib.request.Request(BASE + path, data=data, method=method) - req.add_header("Authorization", "token " + token) - req.add_header("Content-Type", "application/json") - req.add_header("Accept", "application/json") - try: - with urllib.request.urlopen(req, timeout=30) as r: - return json.loads(r.read().decode() or "null") - except urllib.error.HTTPError as e: - raise SystemExit(f"{method} {path}: HTTP {e.code} {e.read().decode()[:300]}") from None +def tea(*args: str, output: str | None = "json") -> list | dict | str: + cmd = ["tea", *args, "--repo", REPO] + if output: + cmd += ["--output", output] + r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8") + if r.returncode != 0: + raise SystemExit(f"tea {' '.join(args)} failed: {r.stderr.strip()[:400]}") + if output == "json": + return json.loads(r.stdout or "[]") + return r.stdout def main() -> int: ap = argparse.ArgumentParser() - ap.add_argument("--token-file", required=True) ap.add_argument("--dry-run", action="store_true") a = ap.parse_args() if a.dry_run: @@ -317,42 +313,46 @@ def main() -> int: print(f"{len(ISSUES)} issues") return 0 - token = Path(a.token_file).read_text(encoding="utf-8").strip() - if not token: - raise SystemExit("token file is empty") - - existing = {lb["name"]: lb["id"] for lb in api("GET", f"/repos/{REPO}/labels?limit=100", token)} + existing = {lb["name"] for lb in tea("labels", "list")} for name, color in LABELS.items(): if name not in existing: - lb = api("POST", f"/repos/{REPO}/labels", token, {"name": name, "color": "#" + color}) - existing[name] = lb["id"] + tea("labels", "create", "--name", name, "--color", color, output=None) print("label created:", name) - titles = set() - page = 1 - while True: - batch = api("GET", f"/repos/{REPO}/issues?state=all&type=issues&limit=50&page={page}", token) - if not batch: - break - titles.update(i["title"] for i in batch) - page += 1 + titles = {i["title"] for i in tea("issues", "list", "--state", "all", "--limit", "500")} made = 0 for key, title, labels, body in ISSUES: if title in titles: print("exists:", title) continue - full = ( - body - + f"\n\n---\nPlan reference: {key} in [docs/PLAN.md]({PLAN}) (open questions). Answer here; the build takes the default until then." + footer = ( + "Plan reference: " + + key + + " in [docs/PLAN.md](" + + PLAN + + ") (open questions). Answer here; the build takes the default until then." ) - issue = api( - "POST", - f"/repos/{REPO}/issues", - token, - {"title": title, "body": full, "labels": [existing[lb] for lb in labels]}, - ) - print(f"created #{issue['number']}: {title}") + full = body + "\n\n---\n" + footer + "\n" + with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as f: + f.write(full) + path = f.name + try: + out = tea( + "issues", + "create", + "--title", + title, + "--labels", + ",".join(labels), + "--description-file", + path, + output=None, + ) + finally: + Path(path).unlink(missing_ok=True) + first = (out or "").strip().splitlines()[0] if out else "" + print(f"created: {title} {first[:60]}") made += 1 print(f"{made} created, {len(ISSUES) - made} already existed") return 0