#!/usr/bin/env python3 """ tea_helper.py — Python utility for Gitea tea CLI automation. Usage: python tea_helper.py issues [--state open|closed|all] [--repo owner/repo] python tea_helper.py prs [--state open|closed|all] [--repo owner/repo] python tea_helper.py releases [--repo owner/repo] python tea_helper.py close-issue [--repo owner/repo] python tea_helper.py close-pr [--repo owner/repo] python tea_helper.py create-issue --title TITLE [--desc DESC] [--labels L1,L2] python tea_helper.py triage [--repo owner/repo] """ import argparse import json import subprocess import sys from typing import Any # --------------------------------------------------------------------------- # Core helpers # --------------------------------------------------------------------------- def run_tea(*args: str, capture: bool = True) -> subprocess.CompletedProcess: cmd = ["tea", *args] result = subprocess.run(cmd, capture_output=capture, text=True) if result.returncode != 0 and capture: print(f"[tea error] {result.stderr.strip()}", file=sys.stderr) return result def tea_json(*args: str) -> Any: result = run_tea(*args, "-o", "json") if result.returncode != 0: return [] try: return json.loads(result.stdout) except json.JSONDecodeError: return [] def repo_flags(repo: str | None) -> list[str]: return ["-r", repo] if repo else [] # --------------------------------------------------------------------------- # Commands # --------------------------------------------------------------------------- def cmd_issues(args: argparse.Namespace) -> None: flags = ["issues", "list", "--state", args.state, *repo_flags(args.repo)] issues = tea_json(*flags) if not issues: print("No issues found.") return print(f"{'#':<6} {'State':<8} {'Title'}") print("-" * 60) for i in issues: print(f"#{i.get('number', i.get('index','?')):<5} {i.get('state',''):<8} {i.get('title','')}") def cmd_prs(args: argparse.Namespace) -> None: flags = ["pulls", "list", "--state", args.state, "--fields", "index,title,state,author,milestone,updated,labels,ci", *repo_flags(args.repo)] prs = tea_json(*flags) if not prs: print("No pull requests found.") return print(f"{'#':<6} {'State':<8} {'Title'}") print("-" * 60) for pr in prs: print(f"#{pr.get('number', pr.get('index','?')):<5} {pr.get('state',''):<8} {pr.get('title','')}") def cmd_releases(args: argparse.Namespace) -> None: flags = ["releases", "list", *repo_flags(args.repo)] releases = tea_json(*flags) if not releases: print("No releases found.") return print(f"{'Tag':<20} {'Name':<30} {'Draft':<6} {'Pre':<5}") print("-" * 65) for r in releases: print(f"{r.get('tag_name',''):<20} {r.get('name',''):<30} " f"{str(r.get('draft', False)):<6} {str(r.get('prerelease', False)):<5}") def cmd_close_issue(args: argparse.Namespace) -> None: result = run_tea("issues", "close", str(args.index), *repo_flags(args.repo)) if result.returncode == 0: print(f"Issue #{args.index} closed.") def cmd_close_pr(args: argparse.Namespace) -> None: result = run_tea("pulls", "close", str(args.index), *repo_flags(args.repo)) if result.returncode == 0: print(f"PR #{args.index} closed.") def cmd_create_issue(args: argparse.Namespace) -> None: flags = ["issues", "create", "--title", args.title, *repo_flags(args.repo)] if args.desc: flags += ["--description", args.desc] if args.labels: flags += ["--labels", args.labels] result = run_tea(*flags, capture=False) sys.exit(result.returncode) def cmd_triage(args: argparse.Namespace) -> None: """Group open issues by their first label.""" flags = ["issues", "list", "--state", "open", *repo_flags(args.repo)] issues = tea_json(*flags) if not issues: print("No open issues.") return groups: dict[str, list] = {} for issue in issues: labels = issue.get("labels") or [] label = labels[0].get("name", "unlabeled") if labels else "unlabeled" groups.setdefault(label, []).append(issue) for label, items in sorted(groups.items()): print(f"\n[{label}] ({len(items)})") for i in items: print(f" #{i.get('number', i.get('index','?'))} {i.get('title','')}") # --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(description="tea CLI helper (Python wrapper)") p.add_argument("--repo", "-r", help="owner/repo slug (overrides auto-detect)") sub = p.add_subparsers(dest="command", required=True) # issues pi = sub.add_parser("issues", help="List issues") pi.add_argument("--state", default="open", choices=["open", "closed", "all"]) # prs pp = sub.add_parser("prs", help="List pull requests") pp.add_argument("--state", default="open", choices=["open", "closed", "all"]) # releases sub.add_parser("releases", help="List releases") # close-issue pci = sub.add_parser("close-issue", help="Close an issue") pci.add_argument("index", type=int, help="Issue index/number") # close-pr pcp = sub.add_parser("close-pr", help="Close a pull request") pcp.add_argument("index", type=int, help="PR index/number") # create-issue pcr = sub.add_parser("create-issue", help="Create a new issue") pcr.add_argument("--title", "-t", required=True) pcr.add_argument("--desc", "-d", default="") pcr.add_argument("--labels", "-L", default="", help="Comma-separated labels") # triage sub.add_parser("triage", help="Group open issues by label") return p def main() -> None: parser = build_parser() args = parser.parse_args() dispatch = { "issues": cmd_issues, "prs": cmd_prs, "releases": cmd_releases, "close-issue": cmd_close_issue, "close-pr": cmd_close_pr, "create-issue": cmd_create_issue, "triage": cmd_triage, } dispatch[args.command](args) if __name__ == "__main__": main()