初始化專案:Go 骨架與 CLI 進入點 #2
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# 建置產物
|
||||||
|
/teai
|
||||||
|
/teai.exe
|
||||||
|
/bin/
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# 測試與覆蓋率
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
coverage.*
|
||||||
|
|
||||||
|
# 環境與編輯器
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# 作業系統
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# teai
|
||||||
|
|
||||||
|
teai 是 Gitea 的命令列(CLI)輔助工具,補足官方 `tea` CLI 缺少的功能。
|
||||||
|
名稱取「**te**a + **AI**」:這套工具主要為了讓 AI agent 能以更完整、更可靠的
|
||||||
|
方式操作 Gitea(查詢、回覆、審核、自動化……)。
|
||||||
|
|
||||||
|
- **語言:** Go(與 Gitea 本體一致),僅用標準庫,離線可建置
|
||||||
|
- **目標站點:** `https://gitea.alterminal.com`
|
||||||
|
- **狀態:** 專案初始化中,功能尚未開始開發;本文的指令集為設計藍圖
|
||||||
|
|
||||||
|
## 安裝與建置
|
||||||
|
|
||||||
|
需要 Go 1.26 以上。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go build -o teai ./cmd/teai
|
||||||
|
```
|
||||||
|
|
||||||
|
## 指令設計
|
||||||
|
|
||||||
|
> 工作流規則的唯一來源是 [`alterminal/agents`](https://gitea.alterminal.com/alterminal/agents) 的 `AGENTS.md`;
|
||||||
|
> 現行原型為 `agents/scripts/lib/gitea.py`(Python)。teai 的指令集以它為行為基準,用 Go 重寫並納入本 CLI。
|
||||||
|
|
||||||
|
### 設計原則
|
||||||
|
|
||||||
|
1. **與 tea 互補,不重複**:`tea` 已有的功能(clone、issues、pulls、labels、milestones、release、api……)一律用 `tea`。teai 只做 tea 做不到的事:跨倉庫/跨組織聚合、工作流判定(跟進/審核/停滯)、機器可讀輸出。
|
||||||
|
2. **規則單一來源**:「最後一則留言不是自己」等工作流判定規則以 `AGENTS.md` 為準,teai 只實作、不改義。規則要變更,先改 AGENTS.md,teai 跟著改。
|
||||||
|
3. **機器可讀優先**:預設輸出 JSON;`--output table` 僅供人工抽查。
|
||||||
|
4. **可直接替換原型**:`gitea.py` 的每個子命令在 teai 都有語義相同的對應命令,可交叉驗證、平滑遷移。
|
||||||
|
5. **認證沿用 tea**:讀 `tea` 的登入組態(`TEA_CONFIG` 或 `~/.config/tea/config.yml`),不另建認證體系。
|
||||||
|
6. **安全預設**:本期全部是查詢類(唯讀);寫入類(留言、指派、審核者)不在本期範圍,未來加入時須明確 `--yes`。
|
||||||
|
|
||||||
|
### 全域介面
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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 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` — 下一件該做的事
|
||||||
|
|
||||||
|
```sh
|
||||||
|
teai next # JSON;沒有工作 → 輸出 null,exit 0
|
||||||
|
teai next --output table # 人類可讀
|
||||||
|
```
|
||||||
|
|
||||||
|
拾取規則(與 AGENTS.md/gitea.py 完全一致,最久未更新者優先):
|
||||||
|
|
||||||
|
1. 自己發起、最後一則留言不是自己的 open PR(role=author)
|
||||||
|
2. 自己被指定為審核者、最後一則留言不是自己的 open PR(role=reviewer,含尚無留言)
|
||||||
|
3. 分派給自己、最後一則留言不是自己的 open issue
|
||||||
|
|
||||||
|
輸出(與 `find_next_work()` 同欄位):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "pull",
|
||||||
|
"role": "author",
|
||||||
|
"repo": "alterminal/teai",
|
||||||
|
"number": 2,
|
||||||
|
"title": "初始化專案:Go 骨架與 CLI 進入點",
|
||||||
|
"url": "https://gitea.alterminal.com/alterminal/teai/pulls/2"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `teai stalled [--hours N]` — 管理者巡邏
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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 陣列,停滯由長到短;無停滯項目 → 空陣列 `[]`。
|
||||||
|
|
||||||
|
每筆欄位:
|
||||||
|
|
||||||
|
```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]`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
teai members # Agents 團隊全體成員(去重、排序)
|
||||||
|
teai members --has-work # 只列有未完成工作的成員(判定同 next)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `teai pulls [filters]`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
teai pulls # 有留言且最後一則不是作者的 open PR(跨倉庫)
|
||||||
|
teai pulls --mine # 再限定:作者是我(我該跟進的 PR)
|
||||||
|
teai pulls --reviewer # 我是 requested reviewer、最後留言不是自己(含尚無留言)
|
||||||
|
teai pulls --repo alterminal/teai # 限定單一倉庫
|
||||||
|
```
|
||||||
|
|
||||||
|
`--mine` 與 `--reviewer` 不可併用。
|
||||||
|
|
||||||
|
### `teai mine`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
teai mine # 分派給我、最後一則留言不是自己的 open issues(跨所屬組織)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `teai orgs` / `teai whoami`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
teai orgs # 我所屬的組織
|
||||||
|
teai whoami # 目前帳號(讀 tea 組態)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `teai xrefs <owner>/<repo> <number>` — 引用解析
|
||||||
|
|
||||||
|
解析該 issue/PR 內文與全部留言中的 `#N`、`repo#N`、`owner/repo#N` 引用,輸出解析後的目標清單(去重):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "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` 為其邏輯的公開化 |
|
||||||
|
|
||||||
|
遷移策略:
|
||||||
|
|
||||||
|
1. 以 gitea.py 為行為基準:同帳號同時跑兩者,輸出應一致(交叉驗證)。
|
||||||
|
2. gitea.py 保留不刪;待 AGENTS.md 改引用 teai 時才停用對應腳本。
|
||||||
|
3. 實作順序:`whoami`/`orgs`/`members`(讀取類)→ `pulls`/`mine`/`next`(判定類)→ `stalled`(最複雜,含 xref)。
|
||||||
|
|
||||||
|
### 安全設計
|
||||||
|
|
||||||
|
- 本期命令全部唯讀;寫入類命令未來加入時須 `--yes` 明確確認。
|
||||||
|
- token 不入輸出、不入日誌;建議用 `TEAI_TOKEN` 或 tea 組態,避免出現在命令列。
|
||||||
|
- 清單類 API 一律帶 `limit`(Gitea 預設上限 50),避免漏抓。
|
||||||
|
- 「有無實際產出」的判斷一律對照遠端實況(API 回傳),不信任回報文字。
|
||||||
|
|
||||||
|
## 開發
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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)。
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// 套件 main 是 teai 的程式進入點。
|
||||||
|
//
|
||||||
|
// teai 是 Gitea 的命令列輔助工具(tea + AI),補足官方 tea CLI 缺少的功能。
|
||||||
|
// 實際的命令分派與執行邏輯位於 internal/cli 套件;main 僅負責把標準
|
||||||
|
// 輸入輸出接上,並以內部錯誤碼結束行程。
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitea.alterminal.com/alterminal/teai/internal/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(cli.Run(os.Stdout, os.Stderr, os.Args[1:]))
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
// 套件 cli 提供 teai 的命令列架構:引數剖析、子命令分派與輸出。
|
||||||
|
//
|
||||||
|
// 設計目標是讓之後新增子命令時只需註冊一個 command 結構,
|
||||||
|
// 不需要改動分派邏輯;也讓核心功能可以被測試(Run 接受 io.Writer)。
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version 是目前開發中的版本號。採用語意化版本;正式發佈前以 0 開頭。
|
||||||
|
var Version = "0.1.0-dev"
|
||||||
|
|
||||||
|
// ExitCode 是 Run 回傳的行程結束碼。
|
||||||
|
type ExitCode int
|
||||||
|
|
||||||
|
// 常見的結束碼定義。0 表示成功,其餘對應常見的命令列錯誤情境。
|
||||||
|
const (
|
||||||
|
ExitOK ExitCode = iota // 成功
|
||||||
|
ExitUsage // 引數或子命令錯誤
|
||||||
|
ExitInternal // 內部錯誤(不該發生)
|
||||||
|
)
|
||||||
|
|
||||||
|
// command 定義一個子命令:名稱、一行說明與實作。
|
||||||
|
type command struct {
|
||||||
|
name string
|
||||||
|
usage string
|
||||||
|
run func(env *Env, args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Env 聚集一次執行所需的輸出目標,便於測試時替換。
|
||||||
|
type Env struct {
|
||||||
|
// Out 是一般輸出(命令結果)。
|
||||||
|
Out io.Writer
|
||||||
|
// Err 是診斷輸出(錯誤、警告)。
|
||||||
|
Err io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// commands 是已註冊的子命令表。新增功能時在這裡註冊即可。
|
||||||
|
var commands = map[string]*command{
|
||||||
|
"version": {
|
||||||
|
name: "version",
|
||||||
|
usage: "顯示版本資訊",
|
||||||
|
run: runVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run 剖析引數並分派到對應子命令,回傳行程結束碼。
|
||||||
|
//
|
||||||
|
// 無引數或要求說明(-h/--help)時印出用法;未知子命令回 ExitUsage。
|
||||||
|
func Run(stdout, stderr io.Writer, args []string) int {
|
||||||
|
env := &Env{Out: stdout, Err: stderr}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
printUsage(env.Out)
|
||||||
|
return int(ExitOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "-h", "--help", "help":
|
||||||
|
printUsage(env.Out)
|
||||||
|
return int(ExitOK)
|
||||||
|
case "-v", "--version", "version":
|
||||||
|
return dispatch(env, "version", args[1:])
|
||||||
|
default:
|
||||||
|
name := args[0]
|
||||||
|
if _, ok := commands[name]; !ok {
|
||||||
|
fmt.Fprintf(stderr, "teai: unknown command %q\n\n", name)
|
||||||
|
printUsage(stderr)
|
||||||
|
return int(ExitUsage)
|
||||||
|
}
|
||||||
|
return dispatch(env, name, args[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch 執行已註冊的子命令,把錯誤轉成結束碼並輸出。
|
||||||
|
func dispatch(env *Env, name string, args []string) int {
|
||||||
|
cmd := commands[name]
|
||||||
|
fs := flag.NewFlagSet("teai "+name, flag.ContinueOnError)
|
||||||
|
fs.SetOutput(env.Err)
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return int(ExitUsage)
|
||||||
|
}
|
||||||
|
if err := cmd.run(env, fs.Args()); err != nil {
|
||||||
|
fmt.Fprintf(env.Err, "teai %s: %v\n", name, err)
|
||||||
|
return int(ExitInternal)
|
||||||
|
}
|
||||||
|
return int(ExitOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVersion 輸出版本資訊。
|
||||||
|
func runVersion(env *Env, args []string) error {
|
||||||
|
fmt.Fprintf(env.Out, "teai version %s\n", Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUsage 印出用法與已註冊的子命令清單(依名稱排序)。
|
||||||
|
func printUsage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, "teai — Gitea CLI 輔助工具(tea + AI)\n\n")
|
||||||
|
fmt.Fprintf(w, "用法:\n teai [命令] [參數]\n\n命令:\n")
|
||||||
|
names := make([]string, 0, len(commands))
|
||||||
|
for name := range commands {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
fmt.Fprintf(w, " %-10s %s\n", name, commands[name].usage)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n說明:\n -h, --help 顯示說明\n -v, --version 顯示版本\n")
|
||||||
|
fmt.Fprintf(w, "\n更多資訊:https://gitea.alterminal.com/alterminal/teai\n")
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// cli_test.go 驗證命令列架構的基本行為:用法輸出、子命令分派、
|
||||||
|
// 版本輸出與未知命令的錯誤處理。
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// run 是測試輔助:以 buffer 收集輸出並執行 Run。
|
||||||
|
func run(args ...string) (stdout, stderr string, code int) {
|
||||||
|
var out, errb bytes.Buffer
|
||||||
|
code = Run(&out, &errb, args)
|
||||||
|
return out.String(), errb.String(), code
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunNoArgsShowsUsage(t *testing.T) {
|
||||||
|
stdout, _, code := run()
|
||||||
|
if code != 0 {
|
||||||
|
t.Errorf("無引數應回 0,得到 %d", code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout, "teai") || !strings.Contains(stdout, "命令") {
|
||||||
|
t.Errorf("無引數應印出用法說明,得到:\n%s", stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunHelpFlags(t *testing.T) {
|
||||||
|
for _, flagArg := range []string{"-h", "--help", "help"} {
|
||||||
|
stdout, _, code := run(flagArg)
|
||||||
|
if code != 0 {
|
||||||
|
t.Errorf("%s 應回 0,得到 %d", flagArg, code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout, "用法") {
|
||||||
|
t.Errorf("%s 應印出用法,得到:\n%s", flagArg, stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunVersion(t *testing.T) {
|
||||||
|
for _, arg := range []string{"version", "-v", "--version"} {
|
||||||
|
stdout, _, code := run(arg)
|
||||||
|
if code != 0 {
|
||||||
|
t.Errorf("%s 應回 0,得到 %d", arg, code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout, "teai version ") {
|
||||||
|
t.Errorf("%s 應印出版本,得到:%q", arg, stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunUnknownCommand(t *testing.T) {
|
||||||
|
_, stderr, code := run("no-such-command")
|
||||||
|
if code != int(ExitUsage) {
|
||||||
|
t.Errorf("未知命令應回 ExitUsage(%d),得到 %d", int(ExitUsage), code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stderr, "unknown command") {
|
||||||
|
t.Errorf("未知命令應在 stderr 說明,得到:%q", stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user