Files
gitea-cli/docs/superpowers/specs/2026-04-21-gitea-cli-expansion-design.md
timmy faee15bc78 Initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:04:09 +08:00

10 KiB
Raw Permalink Blame History

gitea-cli 擴充設計

日期2026-04-21 狀態:待實作

背景

gitea-cli 目前只有一支 scripts/publish.sh,負責在自架 Gitea 建立 repo 並把目前工作目錄推上去。本 spec 定義將它擴充為完整的 Gitea 操作工具集。

目標

  • 新增三大類功能:repo 管理取用既有 repo推送進階release / tag / 自訂 commit 訊息)
  • 抽出共用 libscripts/lib/common.sh),新舊腳本都使用
  • 完全不破壞 現有 publish.sh 的外部行為:既有呼叫方式仍然有效
  • 維持既有安全不變量token 絕不寫進 .git/config / remote URL / log

非目標(刻意 YAGNI

以下情境在本次範圍,留待真正需要時再加:

  • 批次 clone 所有 repoclone.sh --all
  • Fork / mirror / webhook / collaborator 管理
  • 多站台切換 configGITEA_PROFILE 等)
  • 互動式 UI / 問答精靈Claude 會代問,腳本維持純 CLI
  • GitHub / GitLab 相容

架構

組織方式:多支獨立腳本 + 共用 lib

選擇理由:腳本風格延續既有、每支單一職責、最不破壞現況;相對於單一子命令 CLIshell subcommand 分派需要自己寫會讓單檔肥大。

檔案結構

gitea-cli/
├── SKILL.md
├── README.md
├── CLAUDE.md
├── config.example.env
├── config.env              (gitignored)
├── .gitignore
├── docs/superpowers/specs/ (本 spec 所在)
└── scripts/
    ├── lib/
    │   └── common.sh       (所有腳本 source 此檔)
    ├── publish.sh          (重構後 source lib外部行為不變 + 新增 -m)
    ├── list.sh
    ├── clone.sh
    ├── rename.sh
    ├── visibility.sh
    ├── archive.sh
    ├── delete.sh
    ├── meta.sh
    ├── release.sh
    └── tag.sh

scripts/lib/common.sh 介面

必須放進來(跨腳本重複且短小):

Function 職責
load_config config.envsource → 檢查 GITEA_URL / GITEA_USER / GITEA_TOKEN 非空且非預設值;失敗時統一錯誤訊息
gitea_api <METHOD> <path> [json-body] 封裝 curl回傳 HTTP 狀態碼、把 body 寫入 $GITEA_API_RESP_FILE;呼叫端自行判斷狀態碼。所有新腳本只透過這支呼叫 API,不自己寫 curl
git_push_with_token <branch> 封裝一次性 credential.helper 推送
git_clone_with_token <clean-url> <target-dir> 同上,用於 cloneclone 完自動把 origin 重設為乾淨 URL
infer_repo_from_cwd git remote get-url origin 推斷 repo 名;解析不到回非零
confirm_destructive <prompt> 互動 y/N--yes flag 或 GITEA_YES=1 則直接通過
confirm_exact_match <expected> 要求使用者完整輸入指定字串才通過(給 delete.sh 用);同樣支援 --yes 略過
log_info / log_warn / log_err 統一輸出前綴(▶ / ⚠ / ❌ / ✅ / ✓全部寫到 stderr

刻意不放(留在各腳本):

  • 各腳本的 arg 解析(幾行而已,抽共用反而難讀)
  • 各 API endpoint 的 JSON body 組字串(放在呼叫處比較直觀)
  • publish.sh 的三種 git 狀態分支判斷(是 publish 獨有)

不變量review 時必守)

  1. Token 絕不落地token 僅允許出現在 config.env、環境變數、gitea_api / git_*_with_token 函式內部;不得寫進 .git/config、remote URL、log、stderrstdout
  2. origin URL 永遠乾淨publish.shclone.sh 結束後 origin 都是不含 token 的 URL
  3. publish.sh 外部行為publish.sh [repo] [public|private] 原有參數位置與預設值完全不變;-m "msg"新增選項,不影響舊呼叫方式
  4. set -euo pipefail + trap 清臨時檔:每支腳本都要
  5. stdout 只有結構化結果stderr 放進度訊息

腳本詳細設計

既有腳本(微調)

publish.sh

用法publish.sh [repo] [public|private] [-m "msg"]

  • 新增 -m "msg":若指定,取代預設 commit 訊息(原本是 Initial commit / Update
  • 重構:load_config / gitea_api / git_push_with_token / log_* 改用 lib
  • 三種 git 狀態分支保留在腳本內
  • stdout:最後只印 repo web URL方便 URL=$(publish.sh)
  • stderr:進度訊息

新腳本(方向 1repo 管理)

rename.sh

用法rename.sh <old-name> <new-name>

  • PATCH /repos/{GITEA_USER}/{old} body {"name": "<new>"}
  • 若 cwd 的 git remote get-url origin 指向舊 repo順手 git remote set-url origin <new-clean-url>
  • stdout:新 repo web URL
  • 不需二次確認(重命名非破壞性)

visibility.sh

用法visibility.sh <repo> <public|private>

  • PATCH /repos/{GITEA_USER}/{repo} body {"private": true|false}
  • 不需二次確認

archive.sh

用法archive.sh <repo> [--unarchive] [--yes|-y]

  • PATCH /repos/{GITEA_USER}/{repo} body {"archived": true}--unarchivefalse
  • 互動模式預設需 y/N 確認;--yes / -y / GITEA_YES=1 略過

delete.sh

用法delete.sh <repo> [--yes|-y]

  • DELETE /repos/{GITEA_USER}/{repo}
  • 互動模式:用 confirm_exact_match <repo> 要求使用者重新輸入完整 repo 名稱才動(最高保險)
  • --yes / -y / GITEA_YES=1 略過

meta.sh

用法meta.sh <repo> [--desc "..."] [--topics "a,b,c"]

  • --descPATCH /repos/{owner}/{repo} body {"description": "..."}
  • --topicsPUT /repos/{owner}/{repo}/topics body {"topics": ["a","b","c"]}(覆蓋式,非增量)
  • 兩者可同時給;都不給時印用法並非零退出

新腳本(方向 2取用既有 repo

list.sh

用法list.sh [--private|--public] [--format table|tsv|name]

  • GET /user/repos(自動翻頁,每頁 50
  • --private / --public:過濾可見性,預設顯示全部
  • --format
    • table(預設):對齊欄位 NAME VISIBILITY DESCRIPTION
    • tsvname\tvisibility\tdescription,給管線 / Claude 用
    • name:每行一個 repo 名,給 xargs
  • stdout:清單內容
  • stderr:進度(抓到幾頁、共幾個)

clone.sh

用法clone.sh <repo> [target-dir]

  • 預設 clone 到 ./<repo>,可指定 target
  • 目標目錄已存在且非空 → 失敗(不覆蓋)
  • git_clone_with_token 做 clone完成後把 origin 重設為乾淨 URL
  • stdoutclone 到的絕對路徑

新腳本(方向 3推送進階

release.sh

用法release.sh <tag> [--name "..."] [--notes "..."] [--target <branch>] [--draft] [--prerelease] [--asset <path>]...

  • 從 cwd 的 origin 推斷 repo推不到強制要 --repo <name>
  • 兩段 API
    1. POST /repos/{owner}/{repo}/releases body 組合參數 → 取回 id
    2. 對每個 --assetPOST /repos/{owner}/{repo}/releases/{id}/assets?name=<basename>multipart/form-data
  • Tag 處理Gitea release API 的 tag_name 若不存在會自動建(指到 target_commitish,預設 HEAD。不強制先 tag.sh
  • 錯誤處理
    • 第 1 步失敗 → 直接結束
    • 第 2 步某個 asset 失敗 → 繼續其他 asset最後非零退出並列出失敗的
  • stdout:第一行 release web URL其餘每行一個 asset 下載 URL

tag.sh

用法tag.sh <tag> [-m "msg"]

  • 本地 git tag -a <tag> -m "<msg>"(預設 msg = "Tag <tag>"
  • git_push_with_token 推這個 taggit push origin <tag>
  • 定位:只打 tag 不發 release 的情境;與 release.sh 獨立

Repo 名稱解析原則(跨腳本)

所有需要 repo 名的腳本,若參數省略,呼叫 infer_repo_from_cwd 嘗試從 git remote get-url origin 推斷。推斷不到就要求使用者帶參數。這樣在 repo 裡直接跑 rename.sh newnamerelease.sh v1.0.0 都能用。

錯誤處理與輸出格式

退出碼

  • 0 成功
  • 非零 失敗(不細分子類;錯誤訊息本身會講清楚原因)

stdout / stderr 分流

  • stderr:所有 ▶ / ✓ / ⚠ / ❌ / ✅ 進度訊息與錯誤
  • stdout:僅最終結構化輸出
    • publish.sh / rename.sh → repo web URL
    • list.sh → repo 清單
    • clone.sh → 絕對路徑
    • release.sh → release URL + 每個 asset URL
    • 其他 → 空

既有 publish.sh 變更:原本進度訊息是 stdout會改到 stderr。在終端機看起來一樣兩者都會顯示但若有人 pipe 其 stdout 會只看到最後的 URL。這是刻意設計不視為 regression。

測試策略

無自動化測試。驗收方式:

  1. 既有 publish.sh 使用情境(三種 git 狀態、public/private、已存在 repo全部手動跑過確認行為不變
  2. 每支新腳本在測試用的 Gitea repo 上跑 happy path 一次
  3. 破壞性操作(delete.sh / archive.sh)特別驗證:
    • 不帶 -y 時會提示
    • delete.sh 輸入錯 repo 名會被拒
    • -y 時直接執行
  4. Token 不落地驗收:跑完 publish.shgrep -r "$GITEA_TOKEN" .git/ 應為空

文件同步

SKILL.md

  • description: 擴寫以涵蓋所有擴充後的觸發情境push / clone / list / rename / release / archive / delete / tag…
  • 正文改以「常見情境 → 對應腳本」方式列全部腳本,保留 config.env 一次性設定段落

README.md

  • 新增「腳本總覽」表格:每支腳本一行用法與一句說明
  • 每支新腳本 1-2 段詳細說明 + 範例
  • 「安全設計」段落補上「clone 完 origin 也是乾淨 URL」

CLAUDE.md

  • 補充「擴展為多腳本」的架構敘述
  • 不變量改寫為所有 push / clone 腳本都必須守 token 不落地
  • 新增「scripts/lib/common.sh 的用途與邊界」段落
  • 新增「新腳本的檔案骨架」小範本source lib → parse args → 呼叫 lib → 印結果),讓未來加新腳本的人照抄

config.example.env

不變,仍是三個變數。

實作順序建議

  1. scripts/lib/common.sh(所有後續腳本的底)
  2. 重構 publish.sh 改用 lib新增 -m;手動驗證外部行為不變
  3. list.sh / clone.sh(方向 2最常用
  4. rename.sh / visibility.sh / meta.sh(方向 1非破壞
  5. archive.sh / delete.sh(方向 1破壞性需測確認機制
  6. tag.sh / release.sh(方向 3
  7. 更新 SKILL.md / README.md / CLAUDE.md
  8. 整套驗收(上節「測試策略」)