// workflow_commands.go — gitea.py 工作流命令的 CLI 接線(#6): // whoami、role、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: "role", usage: "目前角色(Supervisor→supervisor,Agents→worker)", run: runRole}) 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 形式") // newScannerFunc 建立工作流 Scanner;正式環境用 API client。 // 宣告為變數讓接線層測試可注入假 client(不走網路), // 彌補 workflow 套件單元測試覆蓋不到 CLI 分派路徑的缺口。 var newScannerFunc = newWorkflowClient // 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 := newScannerFunc(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}, ) } // runRole:teai role。不在 Supervisor/Agents 團隊時輸出 null(exit 0)。 func runRole(env *Env, args []string) error { if len(args) != 0 { return &ErrUsage{Msg: "role 不接受參數"} } s, err := newScannerFunc(env) if err != nil { return err } role, err := s.Role() if err != nil { return err } p := printer(env) if role == "" { return p.PrintNull() } return p.PrintOne( []gitea.Field{{Name: "role"}}, gitea.Values{role}, ) } // runOrgs:teai orgs。 func runOrgs(env *Env, args []string) error { if len(args) != 0 { return &ErrUsage{Msg: "orgs 不接受參數"} } s, err := newScannerFunc(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 := parseFlags(fs, args); err != nil { return err } if fs.NArg() != 0 { return &ErrUsage{Msg: "members 不接受位置參數"} } s, err := newScannerFunc(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 := newScannerFunc(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 := parseFlags(fs, args); err != nil { return err } 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 := newScannerFunc(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 := newScannerFunc(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 }