#!/usr/bin/env python3 """ comment.py — post or edit a comment on a synced issue. The last issue operation that used to be hand-rolled (`mkdir tmp/comment`, `jq -Rs`, `tea api -X POST`). Entity commands like `tea comment` hang on a multi-line body — an empty-looking positional triggers the $EDITOR fallback on a TTY that does not exist — so everything goes through `tea api` with the payload written to a file first. comment.py wire-sqlc-appclick --file notes.md comment.py wire-sqlc-appclick --body "готово, задеплоено" comment.py wire-sqlc-appclick --file fix.md --edit 1234 The target is a local id, not a number: this layer resolves it through the `gitea:` field. A local-only issue cannot be commented on — there is nothing to comment on yet. After a successful write the comment thread is refetched into .comments.md so the local copy is not stale. Comments are pull-only in the store: nothing round-trips them back, and editing .comments.md by hand changes nothing in Gitea. Login: the operator's pin from .claude/settings.local.json (see /tea:auth). """ import argparse import os import sys _HERE = os.path.dirname(os.path.abspath(__file__)) sys.path[:0] = [_HERE, os.path.normpath(os.path.join(_HERE, "..", "..", "issue", "scripts"))] import _gitea # noqa: E402 import issue # noqa: E402 import map as gmap # noqa: E402 def main(): ap = argparse.ArgumentParser(description="Comment on a synced issue") ap.add_argument("id", help="local issue id (must already be in Gitea)") src = ap.add_mutually_exclusive_group(required=True) src.add_argument("--file", help="markdown file holding the comment body") src.add_argument("--body", help="comment body inline (short, single-line)") ap.add_argument("--edit", type=int, metavar="COMMENT_ID", help="PATCH an existing comment instead of posting a new one") ap.add_argument("--repo", help="owner/repo (default: auto-detect from CWD git remote)") ap.add_argument("--out", default=issue.ISSUE_ROOT, help="store root (default: /tmp/issues)") args = ap.parse_args() root = args.out if not issue.store_exists(root): _gitea.die("store %s does not exist — nothing was created" % root) if not os.path.isfile(issue.path_of(root, args.id)): _gitea.die("no issue %r in %s" % (args.id, root)) iss = issue.load(root, args.id) number = gmap.number_of(iss) if not number: _gitea.die("%s is local-only (no gitea: field) — push it first" % args.id) if args.file: if not os.path.isfile(args.file): _gitea.die("no such file: %s" % args.file) with open(args.file) as f: body = f.read().strip() else: body = args.body.strip() if not body: _gitea.die("empty comment body") login = _gitea.require_login() base = _gitea.repo_base(args.repo) if args.edit: got = _gitea.api(login, "%s/issues/comments/%d" % (base, args.edit), "PATCH", {"body": body}, payload_name="comment-%d" % args.edit) verb = "edited" else: got = _gitea.api(login, "%s/issues/%d/comments" % (base, number), "POST", {"body": body}, payload_name="comment-%s" % args.id) verb = "posted" if not isinstance(got, dict) or "id" not in got: _gitea.die("%s failed, unexpected response" % verb) comments = _gitea.get_comments(login, base, number) cpath = os.path.join(root, "%s.comments.md" % args.id) if comments: with open(cpath, "w") as f: f.write(gmap.render_comments(comments)) elif os.path.isfile(cpath): os.remove(cpath) print("%s comment %s on %s (#%d) %s" % (verb, got["id"], args.id, number, got.get("html_url", ""))) print("thread: %s (%d comment(s))" % (cpath, len(comments))) if __name__ == "__main__": main()