Files
teai/internal/cli/workflow_commands.go
T
alex 7badd9e46d internal/dailyops + cli:日常操作命令 issues/pulls/labels/milestones/releases/repos/api(#8)
- internal/gitea:用戶端支援寫入動詞(DoJSON/DoRaw,共用 do 核心)
- internal/dailyops:七組日常操作的 API 層(issues/pulls/labels/milestones/releases/repos/api)
- internal/cli:子命令接線;寫入類一律 --yes 閘門(未確認不發請求、exit 2)
- teai pulls 相容 #6 工作流用法(無子命令);有子命令走日常操作
- 修正 dispatch:命令旗標(如 --has-work、--comments)不再被誤判為未知全域選項
- teai api 支援 path 內含查詢字串
- 單元測試(httptest 假伺服器)+真實站點交叉驗證(讀取命令輸出對照 Gitea API;寫入回路 create→comment→close 於 alex/.profile 驗證後清理)
2026-09-10 09:19:03 +08:00

244 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// workflow_commands.go — gitea.py 工作流命令的 CLI 接線(#6):
// whoami、orgs、members [--has-work]、mine、pulls [--mine|--reviewer] [--repo]、next。
//
// 輸出走 internal/gitea 的 Printer(--output json|table);錯誤經 cli.exitCodeFor
// 映射結束碼(用法 2/API 3)。token 解析選序見 internal/gitea/auth.go。
package cli
import (
"errors"
"flag"
"fmt"
"strings"
"gitea.alterminal.com/alterminal/teai/internal/gitea"
"gitea.alterminal.com/alterminal/teai/internal/workflow"
)
func init() {
register := func(c *command) {
commands[c.name] = c
}
register(&command{name: "whoami", usage: "目前帳號", run: runWhoami})
register(&command{name: "orgs", usage: "我所屬的組織", run: runOrgs})
register(&command{name: "members", usage: "Agents 團隊成員;--has-work 只列有未完成工作者", run: runMembers})
register(&command{name: "mine", usage: "分派給我、待跟進的 issues", run: runMine})
// pulls 由 dailyops_commands.go 註冊(#8):無子命令時走本檔的 runPulls
// 工作流清單,有子命令(list/view/create/merge/comment)走日常操作。
register(&command{name: "next", usage: "下一件該做的事", run: runNext})
}
// errNoRepoFlag 是 --repo 值格式錯誤。
var errNoRepoFlag = errors.New("--repo 需為 owner/repo 形式")
// newWorkflowClient 依全域選項建立 workflow 所需的 API client。
func newWorkflowClient(env *Env) (*workflow.Scanner, error) {
token, _, err := gitea.ResolveToken(gitea.AuthOptions{
FlagToken: env.Globals.Token,
SiteURL: env.Globals.URL,
ConfigPath: env.Globals.ConfigPath,
})
if err != nil {
return nil, err
}
client, err := gitea.New(env.Globals.URL, token, env.Globals.Timeout)
if err != nil {
return nil, err
}
return &workflow.Scanner{Client: &workflow.APIClient{Client: client}}, nil
}
// printer 依全域選項建立輸出器。
func printer(env *Env) gitea.Printer {
return gitea.Printer{Format: env.Globals.Output, Out: env.Out}
}
// runWhoami:teai whoami。
func runWhoami(env *Env, args []string) error {
if len(args) != 0 {
return &ErrUsage{Msg: "whoami 不接受參數"}
}
s, err := newWorkflowClient(env)
if err != nil {
return err
}
name, err := s.Whoami()
if err != nil {
return err
}
return printer(env).PrintOne(
[]gitea.Field{{Name: "username"}},
gitea.Values{name},
)
}
// runOrgs:teai orgs。
func runOrgs(env *Env, args []string) error {
if len(args) != 0 {
return &ErrUsage{Msg: "orgs 不接受參數"}
}
s, err := newWorkflowClient(env)
if err != nil {
return err
}
orgs, err := s.Orgs()
if err != nil {
return err
}
rows := make([]gitea.Values, 0, len(orgs))
for _, o := range orgs {
rows = append(rows, gitea.Values{o})
}
return printer(env).PrintList(
[]gitea.Field{{Name: "username"}},
rows,
)
}
// runMembers:teai members [--has-work]。輸出每行一個帳號(同 gitea.py)。
func runMembers(env *Env, args []string) error {
fs := flag.NewFlagSet("teai members", flag.ContinueOnError)
fs.SetOutput(env.Err)
hasWork := fs.Bool("has-work", false, "只列出有未完成工作的成員")
if err := fs.Parse(args); err != nil {
return &ErrUsage{Msg: err.Error()}
}
if fs.NArg() != 0 {
return &ErrUsage{Msg: "members 不接受位置參數"}
}
s, err := newWorkflowClient(env)
if err != nil {
return err
}
names, err := s.Members(*hasWork)
if err != nil {
return err
}
// gitea.py members 每行輸出一個帳號(非 JSON);保持一致以便交叉驗證。
for _, name := range names {
fmt.Fprintln(env.Out, name)
}
return nil
}
// runMine:teai mine。
func runMine(env *Env, args []string) error {
if len(args) != 0 {
return &ErrUsage{Msg: "mine 不接受參數"}
}
s, err := newWorkflowClient(env)
if err != nil {
return err
}
issues, err := s.Mine()
if err != nil {
return err
}
return printer(env).PrintList(
[]gitea.Field{
{Name: "repo"}, {Name: "number"}, {Name: "title"}, {Name: "url"},
},
issueRows(issues),
)
}
// runPulls:teai pulls [--mine|--reviewer] [--repo owner/repo]。
func runPulls(env *Env, args []string) error {
fs := flag.NewFlagSet("teai pulls", flag.ContinueOnError)
fs.SetOutput(env.Err)
mine := fs.Bool("mine", false, "限定作者是目前帳號")
reviewer := fs.Bool("reviewer", false, "限定自己是 requested reviewer")
repo := fs.String("repo", "", "限定單一倉庫(owner/repo)")
if err := fs.Parse(args); err != nil {
return &ErrUsage{Msg: err.Error()}
}
if fs.NArg() != 0 {
return &ErrUsage{Msg: "pulls 不接受位置參數"}
}
if *mine && *reviewer {
return &ErrUsage{Msg: "--mine 與 --reviewer 不能併用"}
}
var owner, name string
if *repo != "" {
parts := strings.SplitN(*repo, "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return &ErrUsage{Msg: errNoRepoFlag.Error()}
}
owner, name = parts[0], parts[1]
}
s, err := newWorkflowClient(env)
if err != nil {
return err
}
items, err := s.Pulls(*mine, *reviewer)
if err != nil {
return err
}
rows := make([]gitea.Values, 0, len(items))
for _, item := range items {
if *repo != "" && (item.Owner != owner || item.Repo != name) {
continue
}
rows = append(rows, gitea.Values{
item.Owner + "/" + item.Repo,
item.Pull.Number,
item.Pull.Title,
item.Pull.WebURL(),
})
}
return printer(env).PrintList(
[]gitea.Field{
{Name: "repo"}, {Name: "number"}, {Name: "title"}, {Name: "url"},
},
rows,
)
}
// runNext:teai next。無工作輸出 null(exit 0)。
func runNext(env *Env, args []string) error {
if len(args) != 0 {
return &ErrUsage{Msg: "next 不接受參數"}
}
s, err := newWorkflowClient(env)
if err != nil {
return err
}
work, err := s.Next()
if err != nil {
return err
}
p := printer(env)
if work == nil {
return p.PrintNull()
}
// gitea.py 的 _work_dict:role 僅 pull 帶(issue 不輸出 role 欄位)。
fields := []gitea.Field{
{Name: "type"}, {Name: "role"}, {Name: "repo"},
{Name: "number"}, {Name: "title"}, {Name: "url"},
}
row := gitea.Values{work.Type, work.Role, work.Repo, work.Number, work.Title, work.URL}
if work.Role == "" {
fields = []gitea.Field{
{Name: "type"}, {Name: "repo"},
{Name: "number"}, {Name: "title"}, {Name: "url"},
}
row = gitea.Values{work.Type, work.Repo, work.Number, work.Title, work.URL}
}
return p.PrintOne(fields, row)
}
// issueRows 把 issues 轉成輸出列(repo 欄位由掃描脈絡補齊)。
func issueRows(issues []workflow.Issue) []gitea.Values {
rows := make([]gitea.Values, 0, len(issues))
for i := range issues {
issue := &issues[i]
rows = append(rows, gitea.Values{
issue.FullName(),
issue.Num(),
issue.Title,
issue.WebURL(),
})
}
return rows
}