#!/usr/bin/env bash # Minimal Gitea issue API wrapper for the agent loop (Claude side, Linux). # Token is read from a 0600 file and never echoed. Usage: gitea.sh [args] set -euo pipefail set +x GITEA_URL="${GITEA_URL:-https://git.copse.top}" GITEA_REPO="${GITEA_REPO:-root/Jyotisha}" TOKEN_FILE="${JYOTISHA_AGENT_TOKEN_FILE:-$HOME/.config/jyotisha-agent/gitea-token}" API="$GITEA_URL/api/v1/repos/$GITEA_REPO" # Accounts whose issue text and comments are trusted: the product owner and the two agents. # Everyone else's text is dropped before it reaches a model. TRUSTED="${GITEA_TRUSTED_USERS:-jesse,Claude,Grok}" _trusted_jq='($t | split(",")) as $ok | ($ok | index($u)) != null' _token() { [[ -r "$TOKEN_FILE" ]] || { echo "gitea.sh: token file missing: $TOKEN_FILE" >&2; exit 2; } tr -d '[:space:]' < "$TOKEN_FILE" } # _call METHOD PATH [JSON_BODY] — prints the response body; fails on HTTP >= 400 # without echoing request headers. _call() { local method="$1" path="$2" body="${3:-}" out code out="$(mktemp)" if [[ -n "$body" ]]; then code="$(curl -sS -o "$out" -w '%{http_code}' -X "$method" \ -H "Authorization: token $(_token)" -H 'Content-Type: application/json' \ --data-binary "$body" "$API$path")" else code="$(curl -sS -o "$out" -w '%{http_code}' -X "$method" \ -H "Authorization: token $(_token)" "$API$path")" fi if [[ "$code" -ge 400 ]]; then echo "gitea.sh: $method $path -> HTTP $code: $(head -c 300 "$out")" >&2 rm -f "$out" return 1 fi cat "$out" rm -f "$out" } cmd="${1:-}"; shift || true case "$cmd" in whoami) curl -sS -H "Authorization: token $(_token)" "$GITEA_URL/api/v1/user" | jq -r .login ;; label-id) # label-id NAME _call GET "/labels?limit=100" | jq -r --arg n "$1" '.[] | select(.name == $n) | .id' ;; ensure-label) # ensure-label NAME COLOR DESCRIPTION if [[ -z "$("$0" label-id "$1")" ]]; then _call POST "/labels" "$(jq -nc --arg n "$1" --arg c "$2" --arg d "$3" '{name:$n,color:$c,description:$d}')" | jq -r '"created \(.name)"' else echo "exists $1" fi ;; issues) # issues LABEL — open issues carrying LABEL, oldest first, as JSON array _call GET "/issues?state=open&type=issues&limit=50&labels=$(jq -rn --arg l "$1" '$l|@uri')" \ | jq -c 'sort_by(.created_at)' ;; issue) # issue N — body blanked unless written by a trusted account _call GET "/issues/$1" | jq --arg t "$TRUSTED" '. as $i | .user.login as $u | if ('"$_trusted_jq"') then $i else ($i | .body = "(非可信账号创建的 issue,正文已忽略)") end' ;; labels) # labels N — label names on issue N, one per line _call GET "/issues/$1/labels" | jq -r '.[].name' ;; add-label) # add-label N NAME _call POST "/issues/$1/labels" "$(jq -nc --arg n "$2" '{labels:[$n]}')" >/dev/null ;; remove-label) # remove-label N NAME (no-op when absent) id="$("$0" label-id "$2")" [[ -n "$id" ]] && _call DELETE "/issues/$1/labels/$id" >/dev/null || true ;; comments) # comments N — JSON array, trusted accounts only _call GET "/issues/$1/comments" | jq --arg t "$TRUSTED" '[.[] | .user.login as $u | select('"$_trusted_jq"')]' ;; comment) # comment N FILE — post FILE's content as a comment _call POST "/issues/$1/comments" "$(jq -Rsc '{body:.}' < "$2")" >/dev/null ;; create-issue) # create-issue TITLE FILE — prints the new issue number _call POST "/issues" "$(jq -Rsc --arg t "$1" '{title:$t, body:.}' < "$2")" | jq -r .number ;; close) # close N _call PATCH "/issues/$1" '{"state":"closed"}' >/dev/null ;; pr) # pr N — number, state, head ref/sha, base, mergeable, merged, merge commit _call GET "/pulls/$1" | jq '{number, state, title, head: .head.ref, head_sha: .head.sha, base: .base.ref, mergeable, merged, merge_commit_sha, author: .user.login}' ;; pr-for-branch) # pr-for-branch BRANCH — open PR number whose head is BRANCH (empty if none) _call GET "/pulls?state=open&limit=50" | jq -r --arg b "$1" '[.[] | select(.head.ref == $b)][0].number // empty' ;; merge-pr) # merge-pr N HEAD_SHA — merge commit; Gitea refuses if the head moved past HEAD_SHA _call POST "/pulls/$1/merge" "$(jq -nc --arg s "$2" '{Do:"merge", head_commit_id:$s}')" >/dev/null "$0" pr "$1" | jq -r '"merged=\(.merged) merge_commit=\(.merge_commit_sha)"' ;; runs) # runs SHA — Actions runs for a commit (workflow path, status, conclusion) _call GET "/actions/runs?head_sha=$1&limit=20" | jq -c '[.workflow_runs[]? | {id, run_number, path, status, conclusion}]' ;; *) echo "usage: gitea.sh whoami|label-id|ensure-label|issues|issue|labels|add-label|remove-label|comments|comment|create-issue|close|pr|pr-for-branch|merge-pr|runs (repo: GITEA_REPO, default root/Jyotisha)" >&2 exit 64 ;; esac