{ pkgs, lib }: # Small shell wrappers that replace ad-hoc curl pipelines for # common Forgejo API operations. Every script reads credentials # from the environment: # # HIVE_FORGE_URL -- base URL, e.g. http://localhost:3000 # HIVE_FORGE_REPO -- default repo, e.g. hyperhive/hyperhive # HYPERHIVE_STATE_DIR -- state dir; forge-token lives here # # All scripts exit non-zero on HTTP errors and print diagnostics # to stderr. let # Common prologue injected into every script: reads token and # defines forge_get / forge_post / forge_patch / forge_delete. commonPrologue = '' : "''${HIVE_FORGE_URL:=http://localhost:3000}" : "''${HIVE_FORGE_REPO:=hyperhive/hyperhive}" _state_dir="''${HYPERHIVE_STATE_DIR:-}" _token_file="''${_state_dir:+$_state_dir/}forge-token" if [ ! -f "$_token_file" ]; then echo "hive-forge: no forge-token at $_token_file" >&2 exit 1 fi _token=$(cat "$_token_file") FORGE_API="$HIVE_FORGE_URL/api/v1" forge_get() { ${pkgs.curl}/bin/curl -sf \ -H "Authorization: token $_token" \ -H "Accept: application/json" \ "$1" } forge_post() { ${pkgs.curl}/bin/curl -sf -X POST \ -H "Authorization: token $_token" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "$2" "$1" } forge_patch() { ${pkgs.curl}/bin/curl -sf -X PATCH \ -H "Authorization: token $_token" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "$2" "$1" } forge_delete() { local _url="$1" local _body="''${2:-}" if [ -n "$_body" ]; then ${pkgs.curl}/bin/curl -sf -X DELETE \ -H "Authorization: token $_token" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "$_body" "$_url" else ${pkgs.curl}/bin/curl -sf -X DELETE \ -H "Authorization: token $_token" \ -H "Accept: application/json" \ "$_url" fi } ''; mkScript = name: text: pkgs.writeShellApplication { inherit name; runtimeInputs = [ pkgs.curl pkgs.jq ]; text = commonPrologue + "\n" + text; }; in pkgs.symlinkJoin { name = "hive-forge-tools"; paths = [ # hive-forge-view [repo] # Dump title + body + all comments for an issue or PR. (mkScript "hive-forge-view" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-view [repo]" >&2; exit 1 fi _n="$1" _repo="''${2:-$HIVE_FORGE_REPO}" _issue=$(forge_get "$FORGE_API/repos/$_repo/issues/$_n") _title=$(printf '%s' "$_issue" | jq -r '.title') _body=$(printf '%s' "$_issue" | jq -r '.body // ""') _state=$(printf '%s' "$_issue" | jq -r '.state') _user=$(printf '%s' "$_issue" | jq -r '.user.login') _is_pr=$(printf '%s' "$_issue" | jq -r '.pull_request != null') _kind="issue" if [ "$_is_pr" = "true" ]; then _kind="PR"; fi printf '# %s #%s [%s] by %s\n' "$_kind" "$_n" "$_state" "$_user" printf '%s\n' "$_title" if [ -n "$_body" ]; then printf '\n%s\n' "$_body" fi _comments=$(forge_get "$FORGE_API/repos/$_repo/issues/$_n/comments?limit=50") _count=$(printf '%s' "$_comments" | jq 'length') if [ "$_count" -gt 0 ]; then printf '\n---\n## Comments (%s)\n\n' "$_count" printf '%s' "$_comments" | jq -r '.[] | "**\(.user.login)**: \(.body)\n"' fi '') # hive-forge-issue [repo] # Print key fields of an issue as JSON. (mkScript "hive-forge-issue" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-issue [repo]" >&2; exit 1 fi _n="$1" _repo="''${2:-$HIVE_FORGE_REPO}" forge_get "$FORGE_API/repos/$_repo/issues/$_n" \ | jq '{number,title,state,user:.user.login,assignees:[.assignees[]?.login],labels:[.labels[]?.name],body}' '') # hive-forge-pr [repo] # Print key fields of a PR as JSON. (mkScript "hive-forge-pr" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-pr [repo]" >&2; exit 1 fi _n="$1" _repo="''${2:-$HIVE_FORGE_REPO}" forge_get "$FORGE_API/repos/$_repo/pulls/$_n" \ | jq '{number,title,state,merged,user:.user.login,head_sha:.head.sha,head_branch:.head.label,base_branch:.base.label}' '') # hive-forge-comment [body | -f | - for stdin] # Post a comment on an issue or PR. Body can be passed as: # - trailing args (joined with spaces) # - -f to read from a file # - - or no args to read from stdin (mkScript "hive-forge-comment" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-comment [body | -f | -]" >&2; exit 1 fi _n="$1"; shift if [ $# -eq 0 ] || [ "''${1:-}" = "-" ]; then _body=$(cat) elif [ "''${1:-}" = "-f" ]; then _body=$(cat "$2") else _body="$*" fi _payload=$(jq -n --arg body "$_body" '{body:$body}') forge_post "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/comments" "$_payload" \ | jq '{id,url:.html_url}' '') # hive-forge-assign [--remove] # Assign or unassign a user on an issue or PR. (mkScript "hive-forge-assign" '' if [ $# -lt 2 ]; then echo "usage: hive-forge-assign [--remove]" >&2; exit 1 fi _n="$1"; _user="$2"; _remove="''${3:-}" _payload=$(jq -n --arg u "$_user" '{assignees:[$u]}') if [ "$_remove" = "--remove" ]; then forge_delete "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/assignees" "$_payload" \ | jq '{number,assignees:[.assignees[]?.login]}' else forge_post "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/assignees" "$_payload" \ | jq '{number,assignees:[.assignees[]?.login]}' fi '') # hive-forge-close # Close an issue or PR. (mkScript "hive-forge-close" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-close " >&2; exit 1 fi forge_patch "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$1" '{"state":"closed"}' \ | jq '{number,state}' '') # hive-forge-labels [list | add | remove ] # Manage labels on an issue or PR. Default action is list. (mkScript "hive-forge-labels" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-labels [list|add|remove] [labels...]" >&2; exit 1 fi _n="$1"; _action="''${2:-list}" shift; shift || true case "$_action" in list) forge_get "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/labels" \ | jq '[.[].name]' ;; add) _all=$(forge_get "$FORGE_API/repos/$HIVE_FORGE_REPO/labels?limit=100") _ids=$(printf '%s' "$_all" | jq -r --args '[.[] | select(.name == ($ARGS.positional[])) | .id]' -- "$@") forge_post "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/labels" "{\"labels\":$_ids}" \ | jq '[.[].name]' ;; remove) _all=$(forge_get "$FORGE_API/repos/$HIVE_FORGE_REPO/labels?limit=100") for _label in "$@"; do _id=$(printf '%s' "$_all" | jq -r --arg n "$_label" '.[] | select(.name==$n) | .id') if [ -n "$_id" ]; then forge_delete "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/labels/$_id" fi done forge_get "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/labels" \ | jq '[.[].name]' ;; *) echo "unknown action: $_action (use list, add, remove)" >&2; exit 1 ;; esac '') # hive-forge-pr-reviews [repo] # List reviews on a PR with id/state/user/body. (mkScript "hive-forge-pr-reviews" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-pr-reviews [repo]" >&2; exit 1 fi _n="$1" _repo="''${2:-$HIVE_FORGE_REPO}" forge_get "$FORGE_API/repos/$_repo/pulls/$_n/reviews" \ | jq '[.[] | {id,state,user:.user.login,body,comments_count}]' '') # hive-forge-branches [pattern] [repo] # List branches, optionally filtered by a grep pattern. (mkScript "hive-forge-branches" '' _pattern="''${1:-}" _repo="''${2:-$HIVE_FORGE_REPO}" _result=$(forge_get "$FORGE_API/repos/$_repo/branches?limit=100" \ | jq -r '.[].name') if [ -n "$_pattern" ]; then printf '%s\n' "$_result" | grep "$_pattern" || true else printf '%s\n' "$_result" fi '') # hive-forge-tree-sha [repo] # Print the tree SHA of the commit at the given branch or commit SHA. (mkScript "hive-forge-tree-sha" '' if [ $# -lt 1 ]; then echo "usage: hive-forge-tree-sha [repo]" >&2; exit 1 fi _ref="$1" _repo="''${2:-$HIVE_FORGE_REPO}" # Resolve branch -> commit SHA first, then get the tree SHA # from the git commit object (tree.sha field). _commit_sha=$(forge_get "$FORGE_API/repos/$_repo/branches/$_ref" 2>/dev/null \ | jq -r '.commit.id // empty') || true if [ -z "$_commit_sha" ]; then # Assume it's already a commit SHA _commit_sha="$_ref" fi forge_get "$FORGE_API/repos/$_repo/git/commits/$_commit_sha" \ | jq -r '.tree.sha // .sha' '') ]; }