feat(gitea): 匯入 jsc-gitea 技能組
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: repo-sync
|
||||||
|
description: Batch-sync all readable repos of a chosen Gitea owner into the working directory. List owners, let the user pick, then clone or update each repo; local changes become a branch, commit, push, and PR to develop or master. Use for workspace bootstrap or bulk refresh; not for a single repo.
|
||||||
|
---
|
||||||
|
|
||||||
|
# repo-sync — 存取庫批次同步
|
||||||
|
|
||||||
|
## 步驟
|
||||||
|
|
||||||
|
1. `tools/gitea.sh owners` 列出使用者擁有讀取權限的所有 `{owner}`。
|
||||||
|
2. 依 `jsc-ask:ask` 規則讓使用者選擇要同步的 `{owner}`(選項需標明各 owner 的 repo 數量與影響範圍)。
|
||||||
|
3. `tools/gitea.sh repos {owner}` 列出該 owner 底下可讀取的所有 `{repo}`。
|
||||||
|
4. 逐一同步每個 `{repo}`,此步驟**必須以 sub agent 執行**(每個 repo 一個 sub agent):
|
||||||
|
1. 不存在 → `git clone` 到工作目錄(clone URL 取自 `tools/gitea.sh clone-url`)。
|
||||||
|
2. 已存在 → 切換到 `develop`,沒有就切 `master`(或 `tools/gitea.sh default-branch` 的結果),再 `git pull`。
|
||||||
|
3. 有檔案變更 → 從 develop/master 新建分支,依 `jsc-git:commit` 認可、push,再依 `jsc-git:pr` 建立 PR 回 develop/master。
|
||||||
|
5. 回報每個 repo 的同步結果(clone / 更新 / 建立 PR / 失敗原因)。
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: wiki
|
||||||
|
description: Read or write a Gitea wiki page through tools/gitea.sh with GITEA_TOKEN. Resolve the wiki repo per page type (QUESTION/PLAN/ANALYZE/MAINTAIN/REPO/LOG) via gitea.sh wiki-repo, falling back to JSC_WIKI_REPO or a decision-tree question. Used by jsc-ask, jsc-sdlc, and jsc-log for all wiki pages; not for repo code files.
|
||||||
|
---
|
||||||
|
|
||||||
|
# wiki — Gitea Wiki 頁讀寫
|
||||||
|
|
||||||
|
所有 jsc 技能的 wiki 操作都必須經由本技能,統一入口與權限。
|
||||||
|
|
||||||
|
## 決定 wiki 位置
|
||||||
|
|
||||||
|
不同類型的 wiki 頁可以放在不同的 `{owner}/{repo}`,以頁名前綴分類:`QUESTION`、`PLAN`、`ANALYZE`、`MAINTAIN`、`REPO`、`LOG`。
|
||||||
|
|
||||||
|
1. 執行 `tools/gitea.sh wiki-repo {TYPE}`(TYPE = 頁名前綴)。解析順序:`JSC_WIKI_REPO_{TYPE}` > `JSC_WIKI_REPO`;已設定就直接使用,不再詢問。
|
||||||
|
2. exit 3(都未設定)時依 `jsc-ask:ask` 規則詢問使用者該類型頁面所在的 `{owner}/{repo}`,並建議使用者設定 `JSC_WIKI_REPO_{TYPE}`(各類型可不同)或 `JSC_WIKI_REPO`(共用預設)。
|
||||||
|
|
||||||
|
## 操作
|
||||||
|
|
||||||
|
| 動作 | 指令 |
|
||||||
|
| --- | --- |
|
||||||
|
| 列頁 | `tools/gitea.sh wiki-list {owner}/{repo}` |
|
||||||
|
| 讀頁 | `tools/gitea.sh wiki-get {owner}/{repo} {page}`(不存在 exit 4) |
|
||||||
|
| 寫頁 | 內容先寫入暫存檔,再 `tools/gitea.sh wiki-put {owner}/{repo} {page} {file}`(自動判斷新建或更新) |
|
||||||
|
|
||||||
|
## 規則
|
||||||
|
|
||||||
|
1. 頁面命名必須符合技能準則的「Wiki 頁命名總表」(見 `jsc-meta/references/guidelines.md`)。
|
||||||
|
2. 更新目錄頁(`*_CONTENTS`)時先 `wiki-get` 取回、套用範本附加或修改後整頁 `wiki-put`,不可覆蓋他人條目。
|
||||||
|
3. 內容一律 UTF-8 繁體中文。
|
||||||
Executable
+125
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# gitea.sh — Gitea API 工具(curl + GITEA_TOKEN)。
|
||||||
|
# 用法:
|
||||||
|
# gitea.sh owners # 列出可讀取的 owner(自己 + 組織)
|
||||||
|
# gitea.sh repos <owner> # 列出 owner 底下可讀取的 repo(全名)
|
||||||
|
# gitea.sh default-branch <owner>/<repo> # 印出預設分支
|
||||||
|
# gitea.sh clone-url <owner>/<repo> # 印出 clone URL
|
||||||
|
# gitea.sh wiki-repo <TYPE> # 解析頁面類型的 wiki 位置:
|
||||||
|
# TYPE = QUESTION|PLAN|ANALYZE|MAINTAIN|REPO|LOG(即頁名前綴)
|
||||||
|
# 依序取 JSC_WIKI_REPO_{TYPE} > JSC_WIKI_REPO;都未設定 exit 3(呼叫端須詢問使用者)
|
||||||
|
# gitea.sh wiki-list <owner>/<repo> # 列出 wiki 頁名
|
||||||
|
# gitea.sh wiki-get <owner>/<repo> <page> # 印出 wiki 頁 markdown;不存在時 exit 4
|
||||||
|
# gitea.sh wiki-put <owner>/<repo> <page> <file> # 建立或更新 wiki 頁(內容取自檔案)
|
||||||
|
# gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 建立 PR,印出 PR URL
|
||||||
|
# gitea.sh api <METHOD> <path> [json-file] # 原始 API 呼叫(path 以 /repos/... 起始)
|
||||||
|
# 環境變數: GITEA_HOST(例 https://gitea.jsc.idv.tw)、GITEA_TOKEN
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
: "${GITEA_HOST:?GITEA_HOST is required}"
|
||||||
|
: "${GITEA_TOKEN:?GITEA_TOKEN is required}"
|
||||||
|
case "$GITEA_HOST" in http://*|https://*) HOST="$GITEA_HOST" ;; *) HOST="https://$GITEA_HOST" ;; esac
|
||||||
|
API="${HOST%/}/api/v1"
|
||||||
|
|
||||||
|
req() { # METHOD path [json-file] -> body(HTTP >= 400 時 exit 4)
|
||||||
|
method="$1"; path="$2"; body_file="${3:-}"
|
||||||
|
if [ -n "$body_file" ]; then
|
||||||
|
out=$(curl -sS -w '\n%{http_code}' -X "$method" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \
|
||||||
|
--data-binary "@$body_file" "$API$path")
|
||||||
|
else
|
||||||
|
out=$(curl -sS -w '\n%{http_code}' -X "$method" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" "$API$path")
|
||||||
|
fi
|
||||||
|
code=$(printf '%s' "$out" | tail -n1)
|
||||||
|
printf '%s\n' "$out" | sed '$d'
|
||||||
|
[ "$code" -lt 400 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
json_field() { # stdin JSON -> 每個物件的指定欄位一行(陣列或單一物件皆可)
|
||||||
|
python3 -c '
|
||||||
|
import json,sys
|
||||||
|
d=json.load(sys.stdin); k=sys.argv[1]
|
||||||
|
items=d if isinstance(d,list) else [d]
|
||||||
|
for i in items:
|
||||||
|
v=i.get(k,"")
|
||||||
|
if v!="" and v is not None: print(v)
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd="${1:?usage: gitea.sh <command> ...}"; shift
|
||||||
|
case "$cmd" in
|
||||||
|
owners)
|
||||||
|
{ req GET "/user" | json_field login
|
||||||
|
req GET "/user/orgs?limit=50" | json_field username; } | sort -u ;;
|
||||||
|
repos)
|
||||||
|
owner="${1:?owner required}"
|
||||||
|
# 分頁抓 owner 的 repo(org 與 user 端點擇一成功)
|
||||||
|
page=1
|
||||||
|
while :; do
|
||||||
|
out=$(req GET "/orgs/$owner/repos?limit=50&page=$page" 2>/dev/null) \
|
||||||
|
|| out=$(req GET "/users/$owner/repos?limit=50&page=$page")
|
||||||
|
names=$(printf '%s' "$out" | json_field full_name)
|
||||||
|
[ -n "$names" ] || break
|
||||||
|
printf '%s\n' "$names"
|
||||||
|
page=$((page+1))
|
||||||
|
done ;;
|
||||||
|
default-branch)
|
||||||
|
or="${1:?owner/repo required}"
|
||||||
|
req GET "/repos/$or" | json_field default_branch ;;
|
||||||
|
clone-url)
|
||||||
|
or="${1:?owner/repo required}"
|
||||||
|
req GET "/repos/$or" | json_field clone_url ;;
|
||||||
|
wiki-repo)
|
||||||
|
type=$(printf '%s' "${1:?TYPE required}" | tr a-z A-Z)
|
||||||
|
eval "v=\${JSC_WIKI_REPO_$type:-}"
|
||||||
|
[ -n "$v" ] || v="${JSC_WIKI_REPO:-}"
|
||||||
|
if [ -n "$v" ]; then printf '%s\n' "$v"; else
|
||||||
|
echo "no wiki repo configured for $type (set JSC_WIKI_REPO_$type or JSC_WIKI_REPO)" >&2; exit 3
|
||||||
|
fi ;;
|
||||||
|
wiki-list)
|
||||||
|
or="${1:?owner/repo required}"
|
||||||
|
req GET "/repos/$or/wiki/pages?limit=200" | json_field title ;;
|
||||||
|
wiki-get)
|
||||||
|
or="${1:?owner/repo required}"; page="${2:?page required}"
|
||||||
|
if ! out=$(req GET "/repos/$or/wiki/page/$page" 2>/dev/null); then
|
||||||
|
echo "wiki page not found: $page" >&2; exit 4
|
||||||
|
fi
|
||||||
|
printf '%s' "$out" | python3 -c '
|
||||||
|
import json,sys,base64
|
||||||
|
d=json.load(sys.stdin)
|
||||||
|
sys.stdout.write(base64.b64decode(d.get("content_base64","")).decode("utf-8"))
|
||||||
|
' ;;
|
||||||
|
wiki-put)
|
||||||
|
or="${1:?owner/repo required}"; page="${2:?page required}"; file="${3:?content file required}"
|
||||||
|
tmp=$(mktemp)
|
||||||
|
python3 -c '
|
||||||
|
import json,sys,base64
|
||||||
|
title,path=sys.argv[1],sys.argv[2]
|
||||||
|
content=open(path,"rb").read()
|
||||||
|
print(json.dumps({"title":title,"content_base64":base64.b64encode(content).decode()}))
|
||||||
|
' "$page" "$file" > "$tmp"
|
||||||
|
if req GET "/repos/$or/wiki/page/$page" >/dev/null 2>&1; then
|
||||||
|
req PATCH "/repos/$or/wiki/page/$page" "$tmp" >/dev/null
|
||||||
|
else
|
||||||
|
req POST "/repos/$or/wiki/new" "$tmp" >/dev/null
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
echo "OK $page" ;;
|
||||||
|
pr-create)
|
||||||
|
or="${1:?owner/repo required}"; head="${2:?head required}"; base="${3:?base required}"
|
||||||
|
title="${4:?title required}"; body_file="${5:?body file required}"
|
||||||
|
tmp=$(mktemp)
|
||||||
|
python3 -c '
|
||||||
|
import json,sys
|
||||||
|
print(json.dumps({"head":sys.argv[1],"base":sys.argv[2],"title":sys.argv[3],
|
||||||
|
"body":open(sys.argv[4],encoding="utf-8").read()}))
|
||||||
|
' "$head" "$base" "$title" "$body_file" > "$tmp"
|
||||||
|
req POST "/repos/$or/pulls" "$tmp" | json_field html_url
|
||||||
|
rm -f "$tmp" ;;
|
||||||
|
api)
|
||||||
|
method="${1:?METHOD}"; path="${2:?path}"; body="${3:-}"
|
||||||
|
req "$method" "$path" $body ;;
|
||||||
|
*)
|
||||||
|
echo "unknown command: $cmd" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user