12 KiB
teai
teai 是 Gitea 的命令列(CLI)工具,以更完整、更可靠的方式操作 Gitea。 名稱取「tea + AI」:這套工具主要為了讓 AI agent 能以更完整、更可靠的 方式操作 Gitea(查詢、回覆、審核、自動化……)。
- 語言: Go(與 Gitea 本體一致),僅用標準庫,離線可建置
- 目標站點:
https://gitea.alterminal.com - 狀態: 基礎建設(#5:internal/gitea)、工作流命令(#6:whoami/orgs/members/mine/pulls/next)與日常操作命令(#8:issues/pulls/labels/milestones/releases/repos/api)已實作;stalled/xrefs(#7)進行中
- 範圍(#3): 不避開 tea 已有的功能;單一工具涵蓋完整操作
安裝與建置
需要 Go 1.26 以上。
go build -o teai ./cmd/teai
指令設計
工作流規則的唯一來源是
alterminal/agents的AGENTS.md; 現行原型為agents/scripts/lib/gitea.py(Python)。teai 的指令集以它為行為基準,用 Go 重寫並納入本 CLI。
設計原則
- 功能自主完整(#3):teai 自行實作完整的功能集,不以「tea 已有」為由略過(tea 本身存在諸多問題,見 #3)。除了 gitea.py 工作流(跨倉庫/跨組織聚合、工作流判定、機器可讀輸出),也涵蓋日常操作(issues/pulls/labels/milestones/release……),tea 只是相容參考與對照組。由單一二進位檔提供一致介面,避免多工具拼接。
- 規則單一來源:「最後一則留言不是自己」等工作流判定規則以
AGENTS.md為準,teai 只實作、不改義。規則要變更,先改 AGENTS.md,teai 跟著改。 - 機器可讀優先:預設輸出 JSON;
--output table僅供人工抽查。 - 可直接替換原型:
gitea.py的每個子命令在 teai 都有語義相同的對應命令,可交叉驗證、平滑遷移。 - 認證沿用 tea:讀
tea的登入組態(TEA_CONFIG或~/.config/tea/config.yml),不另建認證體系。 - 安全預設:查詢類唯讀;寫入類命令(#8 起)一律要求明確
--yes,未確認時不發出任何請求、不產生任何遠端變更。
日常操作(tea 對等指令;#3 不避開 tea 已有功能;#8 已實作)
issues/pulls/labels/milestones/releases/repos/api 的日常操作,自行實作、輸出一致 JSON、共用同一套認證與全域選項。介面語義以 tea/Gitea API 為相容參考。已實作:
# issues
teai issues list --repo <owner>/<repo> [--state open|closed|all]
teai issues view --repo <owner>/<repo> <number> [--comments]
teai issues create --repo <owner>/<repo> --title … [--body …] [--assignees a,b] --yes
teai issues close --repo <owner>/<repo> --yes <number>
teai issues comment --repo <owner>/<repo> --body … --yes <number>
# pulls(無子命令仍是工作流清單:--mine/--reviewer/--repo)
teai pulls list --repo <owner>/<repo> [--state open|closed|all]
teai pulls view --repo <owner>/<repo> <number> [--comments]
teai pulls create --repo <owner>/<repo> --head <branch> [--base main] --title … [--body …] --yes
teai pulls merge --repo <owner>/<repo> [--style merge|rebase|rebase-merge|squash] --yes <number>
teai pulls comment --repo <owner>/<repo> --body … --yes <number>
# labels/milestones/releases/repos
teai labels list --repo <owner>/<repo>
teai labels create --repo <owner>/<repo> --name … [--color #RRGGBB] [--description …] --yes
teai milestones list --repo <owner>/<repo> [--state open|closed|all]
teai milestones create --repo <owner>/<repo> --title … [--description …] [--due RFC3339] --yes
teai releases list --repo <owner>/<repo>
teai repos list [--org <org>]
# 任意 API 呼叫(逃生口)
teai api <path> [--method GET|POST|PATCH|DELETE] [--data '<json>'] [--yes]
--yes閘門:所有寫入類命令(create/close/comment/merge/labels create/milestones create/api 非 GET 動詞)未帶--yes時回 exit 2(用法錯誤),且不發出任何 HTTP 請求。teai api的 path 內含查詢字串(如/repos/a/b/issues?state=closed)時會正確傳遞。
全域介面
teai [全域選項] <命令> [參數]
| 選項 | 說明 |
|---|---|
--url <URL> |
Gitea 站點,預設 https://gitea.alterminal.com |
--token <TOKEN> |
API token;未給則依序嘗試 TEAI_TOKEN 環境變數、tea 登入組態 |
--config <path> |
tea 組態檔路徑,等同 TEA_CONFIG |
--output json|table |
輸出格式,預設 json |
--timeout <dur> |
HTTP 請求逾時,預設 30s |
指令總覽
| 命令 | 對應 gitea.py | 說明 |
|---|---|---|
teai login list|add|default|remove |
— | 管理 tea 相容登入組態(見下) |
teai whoami |
get_current_username |
目前帳號 |
teai orgs |
for_all_organizations |
我所屬的組織 |
teai members [--has-work] |
members [--has-work] |
Agents 團隊成員;--has-work 只列有未完成工作者 |
teai mine |
mine |
分派給我、待跟進的 issues |
teai pulls [--mine|--reviewer] [--repo <owner>/<repo>] |
pulls 系列 |
跨倉庫 PR 跟進清單 |
teai next |
next |
下一件該做的事 |
teai stalled [--hours N] |
stalled |
全組織停滯項目掃描(管理者巡邏) |
teai xrefs <owner>/<repo> <number> |
(內部函式公開化) | 解析內文/留言中的 issue/PR 引用 |
teai version |
— | 版本資訊(骨架已實作) |
gitea.py的底線函式(_pr_needs_author_followup、_resolve_xref_targets等)是內部實作,teai 將其放在internal/套件,不另設 CLI 命令。
teai next — 下一件該做的事
teai next # JSON;沒有工作 → 輸出 null,exit 0
teai next --output table # 人類可讀
拾取規則(與 AGENTS.md/gitea.py 完全一致,最久未更新者優先):
- 自己發起、最後一則留言不是自己的 open PR(role=author)
- 自己被指定為審核者、最後一則留言不是自己的 open PR(role=reviewer,含尚無留言)
- 分派給自己、最後一則留言不是自己的 open issue
輸出(與 find_next_work() 同欄位):
{
"type": "pull",
"role": "author",
"repo": "alterminal/teai",
"number": 2,
"title": "初始化專案:Go 骨架與 CLI 進入點",
"url": "https://gitea.alterminal.com/alterminal/teai/pulls/2"
}
teai login — 管理登入
teai 與 tea 共用同一份組態檔(--config/TEA_CONFIG,預設 ~/.config/tea/config.yml)。
寫入採行級編輯:只動目標項目的行,未知欄位(ssh_* 等)與 preferences 區段逐字保留,
不改壞 tea 也在使用的檔案;檔案權限 0600、暫存檔原子替換。
teai login list # 列出登入(一律不含 token)
teai login add [--url <URL>] [--name <名稱>] [--default=false] [--token <TOKEN>]
# 先以 token 呼叫 GET /user 驗證,成功才寫入(並記下帳號)
teai login default <名稱> # 設定預設登入
teai login remove <名稱> [--yes] # 移除登入;移除預設者時其餘第一筆自動遞補
login add未給--token時自 stdin 讀一行,避免 token 進 shell 歷史;token 驗證失敗(401) 不寫入,結束碼 3。- 未給
--name時:同站(URL 主機相同)已有登入 → 就地更新該項目;否則以 URL 主機為名稱。 - 輸出 JSON(
--output table供人工查看),欄位name/url/user/default,永不輸出 token。
teai stalled [--hours N] — 管理者巡邏
teai stalled # 預設 4 小時門檻
teai stalled --hours 6
teai stalled --hours 48 --output table
- 判定:open issue/PR 的最後活動距今超過 N 小時(預設 4)。
- 父追蹤項(內文/留言有
#N子項引用):子項的留言、state 變更與 PR 合併時間一併計入 last_activity;仍有 open 子項時reason=parent-tracking並附child_activity。 - 冷卻:最後留言出自
ceo的項目 24 小時內不再列入(已催促過),冷卻後仍停滯會再出現。 - 輸出:JSON 陣列,停滯由長到短;無停滯項目 → 空陣列
[]。
每筆欄位:
{
"type": "issue",
"repo": "alterminal/agents",
"number": 30,
"title": "…",
"assignees": ["alex"],
"last_comment_by": "alex",
"last_activity_at": "2026-09-09T22:00:00+08:00",
"stalled_hours": 10.5,
"reason": "assignee-idle"
}
PR 改帶 author 與 reviewers 欄位。reason 取值:
- issue:
no-assignee、assignee-idle、no-commenter、waiting-outside、parent-tracking - PR:
no-reviewer、author-idle、reviewer-idle、no-commenter
teai members [--has-work]
teai members # Agents 團隊全體成員(去重、排序)
teai members --has-work # 只列有未完成工作的成員(判定同 next)
teai pulls [filters]
teai pulls # 有留言且最後一則不是作者的 open PR(跨倉庫)
teai pulls --mine # 再限定:作者是我(我該跟進的 PR)
teai pulls --reviewer # 我是 requested reviewer、最後留言不是自己(含尚無留言)
teai pulls --repo alterminal/teai # 限定單一倉庫
--mine 與 --reviewer 不可併用。
teai mine
teai mine # 分派給我、最後一則留言不是自己的 open issues(跨所屬組織)
teai orgs / teai whoami
teai orgs # 我所屬的組織
teai whoami # 目前帳號(讀 tea 組態)
teai xrefs <owner>/<repo> <number> — 引用解析
解析該 issue/PR 內文與全部留言中的 #N、repo#N、owner/repo#N 引用,輸出解析後的目標清單(去重):
[
{ "owner": "alterminal", "repo": "agents", "number": 32, "source": "body" }
]
stalled 的父追蹤項判定內部呼叫同一套邏輯,不重複實作。
輸出與結束碼
所有命令支援 --output json|table,預設 JSON。結束碼:
| 碼 | 意義 |
|---|---|
| 0 | 成功(含「沒有工作」→ 輸出 null/[]) |
| 2 | 用法錯誤(未知命令/參數) |
| 3 | API 錯誤(連線失敗、401、403、5xx) |
與 gitea.py 的對應與遷移
| gitea.py 子命令 | teai 對應 | 語義 |
|---|---|---|
next |
teai next |
相同:author-PR → reviewer-PR → issue,最久未更新優先 |
stalled [--hours N] |
teai stalled [--hours N] |
相同,含 xref 父子追蹤與冷卻 |
members [--has-work] |
teai members [--has-work] |
相同 |
pulls/pulls --mine/pulls --reviewer |
teai pulls [--mine|--reviewer] |
相同 |
mine |
teai mine |
相同 |
| (底線函式) | internal/ 套件 |
不設 CLI 命令;xrefs 為其邏輯的公開化 |
遷移策略:
- 以 gitea.py 為行為基準:同帳號同時跑兩者,輸出應一致(交叉驗證)。
- gitea.py 保留不刪;待 AGENTS.md 改引用 teai 時才停用對應腳本。
- 實作順序:
whoami/orgs/members(讀取類)→pulls/mine/next(判定類)→stalled(最複雜,含 xref)。
安全設計
- 本期命令全部唯讀;寫入類命令未來加入時須
--yes明確確認。 - token 不入輸出、不入日誌;建議用
TEAI_TOKEN或 tea 組態,避免出現在命令列。 - 清單類 API 一律帶
limit(Gitea 預設上限 50),避免漏抓。 - 「有無實際產出」的判斷一律對照遠端實況(API 回傳),不信任回報文字。
開發
go build ./... # 建置全部套件
go test ./... # 跑全部測試
go vet ./... # 靜態檢查
套件結構:
| 路徑 | 內容 |
|---|---|
cmd/teai/ |
程式進入點(main) |
internal/cli/ |
命令列架構(flag 剖析、子命令分派、輸出) |
internal/ 其他子套件 |
依功能新增(如 internal/gitea、internal/workflow) |
慣例
- 文件與溝通一律使用繁體中文
- 對外行為(CLI 輸出、錯誤訊息)以英文為主,方便除錯與自動化剖析
- 變更一律走 feature 分支 + Pull Request,由審核者合併;不直推
main - Commit 訊息與 PR 標題使用繁體中文或英文皆可,但需簡潔明確
授權
尚未決定(待維護者選定,例如 MIT/Apache-2.0)。