feat(label): add guarded batch label shortcuts

This commit is contained in:
NeeNe 2026-07-02 14:07:55 +08:00
parent 71ca2bb683
commit 5f2dba4974
8 changed files with 669 additions and 128 deletions

View File

@ -392,8 +392,19 @@ gitlink-cli label +create --owner Gitlink --repo forgeplus -n bug -d "Something
# Update a label (unspecified fields are preserved)
gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
# Delete a label
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
# Delete a label safely
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --dry-run
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --yes
# Batch create labels safely
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug fixes;feature:#0075ca:New features' --dry-run
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug fixes;feature:#0075ca:New features' --yes
# Batch delete labels safely
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --dry-run
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --yes
```
### Pull Requests

View File

@ -402,8 +402,19 @@ gitlink-cli label +create --owner Gitlink --repo forgeplus -n bug -d "功能缺
# 更新标签(未指定的字段会被保留)
gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
# 删除标签
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
# 安全删除标签
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --dry-run
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --yes
# 安全批量创建标签
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug 修复;feature:#0075ca:新功能' --dry-run
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug 修复;feature:#0075ca:新功能' --yes
# 安全批量删除标签
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --dry-run
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --yes
```
### Pull Request

View File

@ -0,0 +1,77 @@
# Label batch safety shortcuts
## Background
The label shortcut group already supported listing, creating, updating, and
deleting issue labels. Deletion executed immediately, and larger taxonomy setup
or cleanup workflows still required repeated manual commands or Raw API calls.
This change adds safer destructive operations and first-class batch helpers.
## New and changed shortcuts
- `label +delete` now supports `--dry-run` and requires `--yes` for real
deletion.
- `label +batch-create` creates multiple labels from a semicolon-separated
`name:color:description` list.
- `label +batch-delete` deletes multiple labels from a comma-separated ID list.
## Safety model
Destructive or multi-write commands should be previewed first:
```bash
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --dry-run
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug fixes;feature:#0075ca:New features' --dry-run
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --dry-run
```
After confirmation, pass `--yes`:
```bash
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --yes
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug fixes;feature:#0075ca:New features' --yes
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --yes
```
## Parsing rules
- `label +batch-create --labels` uses semicolons between labels and colons
inside each label spec: `name:color:description`.
- Missing colors default to `#1E90FF`.
- Colors are validated as `#RGB` or `#RRGGBB` before any API call.
- `label +batch-delete --ids` accepts comma-separated positive integer IDs and
removes duplicates before making requests.
## Documentation updates
- README and README.zh-CN include safe delete and batch examples.
- `skills/gitlink-label` documents the new shortcuts and safety model.
- `skills/gitlink-issue-tag` now recommends label shortcuts instead of Raw API
calls for common label workflows.
- `skills/gitlink-stale-issue-manager` uses `label +batch-create` for stale
label bootstrap steps.
## Tests
Unit tests cover:
- single delete dry-run and `--yes` confirmation;
- batch-create dry-run/default preview and real API calls;
- batch-delete dry-run/default preview, de-duplication, and real API calls;
- parser validation for label specs, colors, and ID lists;
- partial batch failure reporting.
Suggested verification:
```bash
go test ./shortcuts/label ./shortcuts
```
Full project verification:
```bash
go test ./...
```

View File

@ -75,25 +75,60 @@ func Shortcuts() []*common.Shortcut {
Description: "Delete an issue label",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Label ID", Required: true},
{Name: "dry-run", Usage: "Preview the delete request without removing the label", Bool: true, Default: "false"},
{Name: "yes", Usage: "Confirm label deletion", Bool: true, Default: "false"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
id, err := ctx.RequireArg("id")
if err != nil {
return err
}
env, err := ctx.CallAPI("DELETE", labelItemPath(ctx, id), nil)
if err != nil {
return err
}
return ctx.Output(env)
Run: runDelete,
},
{
Name: "batch-create",
Description: "Batch create issue labels from semicolon-separated specs",
Flags: []common.Flag{
{Name: "labels", Usage: "Semicolon-separated label specs: name:color:description", Required: true},
{Name: "dry-run", Usage: "Preview labels without creating them", Bool: true, Default: "false"},
{Name: "yes", Usage: "Confirm real batch label creation", Bool: true, Default: "false"},
},
Run: runBatchCreate,
},
{
Name: "batch-delete",
Description: "Batch delete issue labels by IDs",
Flags: []common.Flag{
{Name: "ids", Usage: "Comma-separated label IDs", Required: true},
{Name: "dry-run", Usage: "Preview labels without deleting them", Bool: true, Default: "false"},
{Name: "yes", Usage: "Confirm real batch label deletion", Bool: true, Default: "false"},
},
Run: runBatchDelete,
},
}
}
type labelSpec struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Color string `json:"color" yaml:"color"`
}
type labelBatchRequest struct {
Method string `json:"method" yaml:"method"`
Path string `json:"path" yaml:"path"`
Body map[string]interface{} `json:"body,omitempty" yaml:"body,omitempty"`
}
type labelBatchPreview struct {
Repository string `json:"repository" yaml:"repository"`
DryRun bool `json:"dry_run" yaml:"dry_run"`
Action string `json:"action" yaml:"action"`
Requests []labelBatchRequest `json:"requests" yaml:"requests"`
}
type labelBatchResult struct {
Request labelBatchRequest `json:"request" yaml:"request"`
OK bool `json:"ok" yaml:"ok"`
Data interface{} `json:"data,omitempty" yaml:"data,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"`
}
func runCreate(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
@ -163,6 +198,131 @@ func runUpdate(ctx *common.RuntimeContext) error {
return ctx.Output(env)
}
func runDelete(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
id, err := ctx.RequireArg("id")
if err != nil {
return err
}
path := labelItemPath(ctx, id)
if ctx.Arg("dry-run") == "true" {
return ctx.OutputData(labelBatchPreview{
Repository: repositoryName(ctx),
DryRun: true,
Action: "delete_label",
Requests: []labelBatchRequest{{
Method: "DELETE",
Path: path,
}},
})
}
if ctx.Arg("yes") != "true" {
return fmt.Errorf("label delete is destructive; run with --dry-run first, then pass --yes to confirm")
}
env, err := ctx.CallAPI("DELETE", path, nil)
if err != nil {
return err
}
return ctx.Output(env)
}
func runBatchCreate(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
specs, err := parseLabelSpecs(ctx.Arg("labels"))
if err != nil {
return err
}
requests := make([]labelBatchRequest, 0, len(specs))
for _, spec := range specs {
requests = append(requests, labelBatchRequest{
Method: "POST",
Path: labelPath(ctx),
Body: map[string]interface{}{
"name": spec.Name,
"description": spec.Description,
"color": spec.Color,
},
})
}
if ctx.Arg("dry-run") == "true" || ctx.Arg("yes") != "true" {
return ctx.OutputData(labelBatchPreview{
Repository: repositoryName(ctx),
DryRun: true,
Action: "batch_create_labels",
Requests: requests,
})
}
return runLabelBatchRequests(ctx, "batch_create_labels", requests)
}
func runBatchDelete(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
ids, err := parseLabelIDList(ctx.Arg("ids"))
if err != nil {
return err
}
requests := make([]labelBatchRequest, 0, len(ids))
for _, id := range ids {
requests = append(requests, labelBatchRequest{
Method: "DELETE",
Path: labelItemPath(ctx, id),
})
}
if ctx.Arg("dry-run") == "true" || ctx.Arg("yes") != "true" {
return ctx.OutputData(labelBatchPreview{
Repository: repositoryName(ctx),
DryRun: true,
Action: "batch_delete_labels",
Requests: requests,
})
}
return runLabelBatchRequests(ctx, "batch_delete_labels", requests)
}
func runLabelBatchRequests(ctx *common.RuntimeContext, action string, requests []labelBatchRequest) error {
results := make([]labelBatchResult, 0, len(requests))
succeeded := 0
failed := 0
for _, request := range requests {
env, err := ctx.CallAPI(request.Method, request.Path, request.Body)
result := labelBatchResult{Request: request}
if err != nil {
result.OK = false
result.Error = err.Error()
failed++
} else {
result.OK = env.OK
result.Data = env.Data
if env.OK {
succeeded++
} else {
failed++
}
}
results = append(results, result)
}
if err := ctx.OutputData(map[string]interface{}{
"repository": repositoryName(ctx),
"action": action,
"count": len(requests),
"succeeded": succeeded,
"failed": failed,
"results": results,
}); err != nil {
return err
}
if failed > 0 {
return fmt.Errorf("%d of %d label request(s) failed", failed, len(requests))
}
return nil
}
// fetchLabel looks up a single label by id from the list endpoint. GitLink does
// not expose a single-label GET, so we page through the list and match by id.
// A nil result (label not found) is not an error: the caller falls back to the
@ -200,6 +360,77 @@ func labelItemPath(ctx *common.RuntimeContext, id string) string {
return fmt.Sprintf("%s/%s", labelPath(ctx), url.PathEscape(id))
}
func parseLabelSpecs(raw string) ([]labelSpec, error) {
if strings.TrimSpace(raw) == "" {
return nil, fmt.Errorf("--labels is required")
}
parts := strings.Split(raw, ";")
specs := make([]labelSpec, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
fields := strings.SplitN(part, ":", 3)
name := strings.TrimSpace(fields[0])
if name == "" {
return nil, fmt.Errorf("invalid label spec %q: name is required", part)
}
color := defaultLabelColor
if len(fields) > 1 && strings.TrimSpace(fields[1]) != "" {
color = strings.TrimSpace(fields[1])
}
if err := validateColor(color); err != nil {
return nil, err
}
description := ""
if len(fields) > 2 {
description = strings.TrimSpace(fields[2])
}
specs = append(specs, labelSpec{
Name: name,
Description: description,
Color: color,
})
}
if len(specs) == 0 {
return nil, fmt.Errorf("--labels must contain at least one label spec")
}
return specs, nil
}
func parseLabelIDList(raw string) ([]string, error) {
if strings.TrimSpace(raw) == "" {
return nil, fmt.Errorf("--ids is required")
}
parts := strings.Split(raw, ",")
ids := make([]string, 0, len(parts))
seen := map[string]bool{}
for _, part := range parts {
id := strings.TrimSpace(part)
if id == "" {
continue
}
n, err := strconv.Atoi(id)
if err != nil || n <= 0 {
return nil, fmt.Errorf("invalid label id %q: use positive integer IDs", id)
}
if seen[id] {
continue
}
seen[id] = true
ids = append(ids, id)
}
if len(ids) == 0 {
return nil, fmt.Errorf("--ids must contain at least one label ID")
}
return ids, nil
}
func repositoryName(ctx *common.RuntimeContext) string {
return fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo)
}
func validateColor(color string) error {
if !hexColorPattern.MatchString(color) {
return fmt.Errorf("invalid --color value %q: use a hex color like #1E90FF or #abc", color)

View File

@ -143,11 +143,157 @@ func TestLabelDelete(t *testing.T) {
})
defer server.Close()
if err := runLabelShortcut(t, server, "delete", map[string]string{"id": "7"}); err != nil {
if err := runLabelShortcut(t, server, "delete", map[string]string{"id": "7", "yes": "true"}); err != nil {
t.Fatalf("delete shortcut failed: %v", err)
}
}
func TestLabelDeleteDryRunDoesNotCallAPI(t *testing.T) {
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("delete dry-run should not call API, got: %s %s", r.Method, r.URL.Path)
})
defer server.Close()
if err := runLabelShortcut(t, server, "delete", map[string]string{"id": "7", "dry-run": "true"}); err != nil {
t.Fatalf("delete dry-run failed: %v", err)
}
}
func TestLabelDeleteRequiresYes(t *testing.T) {
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("delete without --yes should not call API, got: %s %s", r.Method, r.URL.Path)
})
defer server.Close()
err := runLabelShortcut(t, server, "delete", map[string]string{"id": "7"})
if err == nil {
t.Fatal("expected delete to require --yes")
}
}
func TestLabelBatchCreateDryRunDoesNotCallAPI(t *testing.T) {
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("batch-create dry-run should not call API, got: %s %s", r.Method, r.URL.Path)
})
defer server.Close()
err := runLabelShortcut(t, server, "batch-create", map[string]string{
"labels": "bug:#ee0701:Bug fixes;feature:#0075ca:New features",
"dry-run": "true",
})
if err != nil {
t.Fatalf("batch-create dry-run failed: %v", err)
}
}
func TestLabelBatchCreateDefaultsToDryRunWithoutYes(t *testing.T) {
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("batch-create without --yes should not call API, got: %s %s", r.Method, r.URL.Path)
})
defer server.Close()
err := runLabelShortcut(t, server, "batch-create", map[string]string{
"labels": "bug:#ee0701:Bug fixes",
})
if err != nil {
t.Fatalf("batch-create default dry-run failed: %v", err)
}
}
func TestLabelBatchCreateWithYesCallsEndpoints(t *testing.T) {
var payloads []map[string]interface{}
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "POST", "/v1/owner/repo/issue_tags.json")
payloads = append(payloads, decodeJSON(t, r))
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
})
defer server.Close()
err := runLabelShortcut(t, server, "batch-create", map[string]string{
"labels": "bug:#ee0701:Bug fixes;feature:#0075ca:New features",
"yes": "true",
})
if err != nil {
t.Fatalf("batch-create failed: %v", err)
}
if len(payloads) != 2 {
t.Fatalf("got %d payloads, want 2", len(payloads))
}
assertEqual(t, payloads[0]["name"], "bug")
assertEqual(t, payloads[0]["color"], "#ee0701")
assertEqual(t, payloads[0]["description"], "Bug fixes")
assertEqual(t, payloads[1]["name"], "feature")
}
func TestLabelBatchCreateReportsPartialFailure(t *testing.T) {
calls := 0
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "POST", "/v1/owner/repo/issue_tags.json")
calls++
if calls == 2 {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("server error"))
return
}
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
})
defer server.Close()
err := runLabelShortcut(t, server, "batch-create", map[string]string{
"labels": "bug:#ee0701:Bug fixes;feature:#0075ca:New features",
"yes": "true",
})
if err == nil {
t.Fatal("expected partial failure to return an error")
}
if calls != 2 {
t.Fatalf("got %d calls, want 2", calls)
}
}
func TestLabelBatchDeleteDryRunDoesNotCallAPI(t *testing.T) {
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("batch-delete dry-run should not call API, got: %s %s", r.Method, r.URL.Path)
})
defer server.Close()
if err := runLabelShortcut(t, server, "batch-delete", map[string]string{"ids": "7,8", "dry-run": "true"}); err != nil {
t.Fatalf("batch-delete dry-run failed: %v", err)
}
}
func TestLabelBatchDeleteDefaultsToDryRunWithoutYes(t *testing.T) {
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("batch-delete without --yes should not call API, got: %s %s", r.Method, r.URL.Path)
})
defer server.Close()
if err := runLabelShortcut(t, server, "batch-delete", map[string]string{"ids": "7,8"}); err != nil {
t.Fatalf("batch-delete default dry-run failed: %v", err)
}
}
func TestLabelBatchDeleteWithYesCallsEndpoints(t *testing.T) {
var paths []string
server := newLabelTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Fatalf("got method %s, want DELETE", r.Method)
}
paths = append(paths, r.URL.Path)
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
})
defer server.Close()
if err := runLabelShortcut(t, server, "batch-delete", map[string]string{"ids": "7,8,7", "yes": "true"}); err != nil {
t.Fatalf("batch-delete failed: %v", err)
}
if len(paths) != 2 {
t.Fatalf("got %d calls, want 2", len(paths))
}
assertEqual(t, paths[0], "/v1/owner/repo/issue_tags/7.json")
assertEqual(t, paths[1], "/v1/owner/repo/issue_tags/8.json")
}
func TestValidateColor(t *testing.T) {
valid := []string{"#1E90FF", "#abc", "#ABCDEF", "#000"}
for _, c := range valid {
@ -170,6 +316,54 @@ func TestLabelIDString(t *testing.T) {
assertEqual(t, labelIDString(nil), "")
}
func TestParseLabelSpecs(t *testing.T) {
specs, err := parseLabelSpecs("bug:#ee0701:Bug fixes; docs::Documentation")
if err != nil {
t.Fatalf("parseLabelSpecs failed: %v", err)
}
if len(specs) != 2 {
t.Fatalf("got %d specs, want 2", len(specs))
}
assertEqual(t, specs[0].Name, "bug")
assertEqual(t, specs[0].Color, "#ee0701")
assertEqual(t, specs[0].Description, "Bug fixes")
assertEqual(t, specs[1].Name, "docs")
assertEqual(t, specs[1].Color, defaultLabelColor)
assertEqual(t, specs[1].Description, "Documentation")
}
func TestParseLabelSpecsRejectsInvalidInput(t *testing.T) {
invalid := []string{"", ":#ee0701:missing name", "bug:red:bad color"}
for _, value := range invalid {
if _, err := parseLabelSpecs(value); err == nil {
t.Fatalf("expected %q to be invalid", value)
}
}
}
func TestParseLabelIDList(t *testing.T) {
ids, err := parseLabelIDList("7, 8,7, 9")
if err != nil {
t.Fatalf("parseLabelIDList failed: %v", err)
}
want := []string{"7", "8", "9"}
if len(ids) != len(want) {
t.Fatalf("got %v, want %v", ids, want)
}
for i := range want {
assertEqual(t, ids[i], want[i])
}
}
func TestParseLabelIDListRejectsInvalidInput(t *testing.T) {
invalid := []string{"", "0", "-1", "abc", "7,abc"}
for _, value := range invalid {
if _, err := parseLabelIDList(value); err == nil {
t.Fatalf("expected %q to be invalid", value)
}
}
}
func runLabelShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
t.Helper()
shortcut := findLabelShortcut(t, name)

View File

@ -5,14 +5,14 @@ description: "项目标记管理:查看、创建、修改、删除 GitLink 仓
metadata:
requires:
bins: ["gitlink-cli"]
cliHelp: "gitlink-cli api --help"
cliHelp: "gitlink-cli label --help"
---
# gitlink-tag项目标记管理
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 所有写入/删除操作前,务必先确认用户意图。**
**CRITICAL — 项目标记通过 `gitlink-cli api` 操作,无需本地 git 命令。**
**CRITICAL — 项目标记优先通过 `gitlink-cli label` 快捷命令操作,无需本地 git 命令。**
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)
@ -23,22 +23,25 @@ metadata:
1. **查看标记** — 列出仓库所有项目标记,支持关键词搜索和精简模式
2. **创建标记** — 创建新标记,设置名称、描述和颜色
3. **修改标记** — 修改已有标记的名称、描述或颜色
4. **删除标记** — 删除不再需要的标记
4. **删除标记** — 安全删除不再需要的标记
5. **批量操作** — 批量创建/删除标记,支持 dry-run 预览和 `--yes` 确认
---
## API 能力说明
## Shortcut 能力说明
GitLink API 对项目标记issue_tags的完整支持
GitLink CLI 对项目标记issue_tags的完整支持
| 操作 | HTTP 方法 | API 路径 | 说明 |
|------|-----------|---------|------|
| 查询标记列表 | GET | `/v1/{owner}/{repo}/issue_tags` | 支持 keyword/only_name/sort_by/sort_direction/limit/page 参数 |
| 创建标记 | POST | `/v1/{owner}/{repo}/issue_tags` | 请求体:{name, description, color} |
| 修改标记 | PATCH | `/v1/{owner}/{repo}/issue_tags/{id}` | 请求体:{name, description, color},路径参数 id 为标记 ID |
| 删除标记 | DELETE | `/v1/{owner}/{repo}/issue_tags/{id}` | 路径参数 id 为标记 ID |
| 操作 | Shortcut | 说明 |
|------|----------|------|
| 查询标记列表 | `label +list` | 支持 keyword/only_name/sort_by/sort_direction 参数 |
| 创建标记 | `label +create` | 参数name、description、color |
| 修改标记 | `label +update` | 按 ID 修改,未指定字段会保留 |
| 删除标记 | `label +delete` | 按 ID 删除,支持 `--dry-run``--yes` |
| 批量创建 | `label +batch-create` | 解析 `name:color:description` 列表,支持 `--dry-run``--yes` |
| 批量删除 | `label +batch-delete` | 解析逗号分隔 ID支持 `--dry-run``--yes` |
> **核心原则:** 所有操作均通过 `gitlink-cli api` 调用,无需本地 git 命令。
> **核心原则:** 优先使用 `gitlink-cli label`。Raw API 仅在快捷命令无法覆盖的调试场景使用
---
@ -48,7 +51,7 @@ GitLink API 对项目标记issue_tags的完整支持
```bash
# 获取项目标记完整列表(含描述、颜色、关联 Issue 数量等)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
```
**返回数据结构**
@ -71,7 +74,7 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
```bash
# 搜索名称中包含关键词的标记
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=bug' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword bug --format json
```
**支持的查询参数**
@ -87,7 +90,7 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=bug' --format j
```bash
# 仅返回名称和 ID适用于选择标记、快速浏览等场景
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --only-name true --format json
```
**返回示例**
@ -107,13 +110,13 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --forma
```bash
# 按 Issue 数量倒序排列(找出最常用的标记)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=issues_count&order_direction=desc' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --sort-by issues_count --sort-direction desc --format json
# 按创建时间正序排列(最早创建的排前面)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=created_on&order_direction=asc' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --sort-by created_on --sort-direction asc --format json
# 按更新时间倒序排列(最近更新的排前面)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=updated_on&order_direction=desc' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --sort-by updated_on --sort-direction desc --format json
```
---
@ -124,7 +127,7 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=updated_on&ord
```bash
# 创建一个项目标记
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"测试11","description":"111","color":"#54ff85"}' --format json
gitlink-cli label +create --owner <owner> --repo <repo> -n "测试11" -d "111" -c "#54ff85" --format json
```
**请求体参数**
@ -165,21 +168,22 @@ AI 创建标记时,如用户未指定颜色,可按标记用途推荐默认
```bash
# 创建标记后,通过关键词搜索确认
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=测试11' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "测试11" --format json
```
### 2.4 批量创建标记
当用户需要一次创建多个标记时,逐个调用创建 API
当用户需要一次创建多个标记时,优先使用批量快捷命令
```bash
# 批量创建标记(逐个调用)
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"bug","description":"Bug 修复","color":"#ee0701"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"feature","description":"新功能","color":"#0075ca"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"documentation","description":"文档相关","color":"#0075ca"}' --format json
# 批量创建标记,先 dry-run 再确认
gitlink-cli label +batch-create --owner <owner> --repo <repo> \
--labels 'bug:#ee0701:Bug 修复;feature:#0075ca:新功能;documentation:#0075ca:文档相关' --dry-run
gitlink-cli label +batch-create --owner <owner> --repo <repo> \
--labels 'bug:#ee0701:Bug 修复;feature:#0075ca:新功能;documentation:#0075ca:文档相关' --yes
# 验证创建结果
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --only-name true --format json
```
> **⚠️ 批量创建前,先查询现有标记,避免创建重复名称的标记。**
@ -194,11 +198,11 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --forma
```bash
# Step 1查询标记列表获取目标标记的 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# 在返回结果中找到目标标记的 id 字段
# Step 2使用 ID 修改标记
gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/:id --body '{"name":"测试11","description":"1112","color":"#54ff85"}' --format json
gitlink-cli label +update --owner <owner> --repo <repo> -i <id> -n "测试11" -d "1112" -c "#54ff85" --format json
```
**请求体参数**(与创建相同):
@ -224,40 +228,40 @@ gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/:id --body '{"name":"测试11"
```bash
# 查询获取 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=旧名称' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "旧名称" --format json
# 假设返回 id=5
# 修改名称(保持描述和颜色不变)
gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/5 --body '{"name":"新名称","description":"原描述","color":"#ee0701"}' --format json
gitlink-cli label +update --owner <owner> --repo <repo> -i 5 -n "新名称" --format json
```
**修改标记颜色**
```bash
# 查询获取 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=bug' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword bug --format json
# 假设返回 id=3
# 修改颜色(保持名称和描述不变)
gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/3 --body '{"name":"bug","description":"Bug 修复","color":"#ff0000"}' --format json
gitlink-cli label +update --owner <owner> --repo <repo> -i 3 -c "#ff0000" --format json
```
**修改标记描述**
```bash
# 查询获取 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=feature' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword feature --format json
# 假设返回 id=7
# 修改描述
gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/7 --body '{"name":"feature","description":"新的功能需求描述","color":"#0075ca"}' --format json
gitlink-cli label +update --owner <owner> --repo <repo> -i 7 -d "新的功能需求描述" --format json
```
### 3.3 修改后验证
```bash
# 修改后查询确认变更已生效
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=修改后的名称' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "修改后的名称" --format json
```
> **⚠️ 修改标记名称后,已关联该标记的 Issue 会自动更新为新名称。**
@ -272,11 +276,12 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=修改后的名
```bash
# Step 1查询标记列表获取目标标记的 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=要删除的标记名' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "要删除的标记名" --format json
# 在返回结果中找到目标标记的 id 字段
# Step 2删除标记
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/:id --format json
# Step 2先预览删除再确认执行
gitlink-cli label +delete --owner <owner> --repo <repo> -i <id> --dry-run
gitlink-cli label +delete --owner <owner> --repo <repo> -i <id> --yes
```
**返回示例**
@ -292,7 +297,7 @@ gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/:id --format json
```bash
# Step 1查看所有标记确认要删除的目标
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# Step 2记录目标标记的 ID 和关联 Issue 数量
# 假设目标标记 id=5, name="deprecated", issues_count=3
@ -301,10 +306,11 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
# ⚠️ 删除标记后,关联的 Issue 将失去该标记
# Step 4执行删除
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/5 --format json
gitlink-cli label +delete --owner <owner> --repo <repo> -i 5 --dry-run
gitlink-cli label +delete --owner <owner> --repo <repo> -i 5 --yes
# Step 5验证删除结果
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=deprecated' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword deprecated --format json
# total_count 应为 0
```
@ -312,18 +318,17 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=deprecated' --f
```bash
# Step 1查询所有标记
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# Step 2AI 根据用户意图筛选要删除的标记,列出 ID 列表
# 假设要删除 id=3, id=5, id=8
# Step 3逐个删除
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/3 --format json
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/5 --format json
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/8 --format json
# Step 3批量删除先 dry-run 再确认
gitlink-cli label +batch-delete --owner <owner> --repo <repo> --ids 3,5,8 --dry-run
gitlink-cli label +batch-delete --owner <owner> --repo <repo> --ids 3,5,8 --yes
# Step 4验证删除结果
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
```
> **⚠️ 批量删除是危险操作,必须先列出待删除标记清单让用户确认后再执行。**
@ -336,7 +341,7 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
```bash
# 获取所有标记及关联 Issue 数量
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=issues_count&order_direction=asc' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --sort-by issues_count --sort-direction asc --format json
```
**AI 分析规则**
@ -355,14 +360,10 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=issues_count&o
```bash
# 基础标记集(适用于大多数项目)
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"bug","description":"Bug 修复或问题报告","color":"#ee0701"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"feature","description":"新功能需求","color":"#0075ca"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"enhancement","description":"功能优化或改进","color":"#5319e7"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"documentation","description":"文档相关","color":"#0075ca"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"good-first-issue","description":"适合新贡献者的问题","color":"#008672"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"help-wanted","description":"需要帮助的问题","color":"#008672"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"question","description":"使用疑问","color":"#fbca04"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"wontfix","description":"不会处理的问题","color":"#fbca04"}' --format json
gitlink-cli label +batch-create --owner <owner> --repo <repo> --dry-run \
--labels 'bug:#ee0701:Bug 修复或问题报告;feature:#0075ca:新功能需求;enhancement:#5319e7:功能优化或改进;documentation:#0075ca:文档相关;good-first-issue:#008672:适合新贡献者的问题;help-wanted:#008672:需要帮助的问题;question:#fbca04:使用疑问;wontfix:#fbca04:不会处理的问题'
gitlink-cli label +batch-create --owner <owner> --repo <repo> --yes \
--labels 'bug:#ee0701:Bug 修复或问题报告;feature:#0075ca:新功能需求;enhancement:#5319e7:功能优化或改进;documentation:#0075ca:文档相关;good-first-issue:#008672:适合新贡献者的问题;help-wanted:#008672:需要帮助的问题;question:#fbca04:使用疑问;wontfix:#fbca04:不会处理的问题'
```
### 5.3 合并相似标记
@ -371,7 +372,7 @@ gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"wontfix","desc
```bash
# Step 1查询所有标记AI 识别相似标记对
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# Step 2假设 "bug"(id=3) 和 "defect"(id=7) 需要合并,保留 "bug"
@ -381,23 +382,24 @@ gitlink-cli issue +list --state open --owner <owner> --repo <repo> --format json
# AI 筛选标记为 "defect" 的 Issue将其改为 "bug"
# Step 4删除 "defect" 标记
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/7 --format json
gitlink-cli label +delete --owner <owner> --repo <repo> -i 7 --dry-run
gitlink-cli label +delete --owner <owner> --repo <repo> -i 7 --yes
# Step 5验证
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
```
### 5.4 查询参数组合使用
```bash
# 搜索关键词 + 仅返回名称
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=bug&only_name=true' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword bug --only-name true --format json
# 按 Issue 数量倒序 + 仅返回名称(快速查看热门标记)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=issues_count&order_direction=desc&only_name=true' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --sort-by issues_count --sort-direction desc --only-name true --format json
# 按更新时间倒序(查看最近活跃的标记)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=updated_on&order_direction=desc' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --sort-by updated_on --sort-direction desc --format json
```
---
@ -408,57 +410,58 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=updated_on&ord
```bash
# Step 1获取项目标记完整列表
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# Step 2可选搜索特定标记
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=bug' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword bug --format json
# Step 3可选查看精简列表
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --only-name true --format json
```
### 6.2 创建标记流程
```bash
# Step 1查看现有标记避免重复
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --only-name true --format json
# Step 2创建标记
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"新标记","description":"标记描述","color":"#54ff85"}' --format json
gitlink-cli label +create --owner <owner> --repo <repo> -n "新标记" -d "标记描述" -c "#54ff85" --format json
# Step 3验证创建结果
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=新标记' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "新标记" --format json
```
### 6.3 修改标记流程
```bash
# Step 1查询目标标记获取 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=要修改的标记' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "要修改的标记" --format json
# 记录目标标记的 id
# Step 2修改标记使用 ID
gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/:id --body '{"name":"修改后名称","description":"修改后描述","color":"#ff0000"}' --format json
gitlink-cli label +update --owner <owner> --repo <repo> -i <id> -n "修改后名称" -d "修改后描述" -c "#ff0000" --format json
# Step 3验证修改结果
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=修改后名称' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "修改后名称" --format json
```
### 6.4 删除标记流程
```bash
# Step 1查询目标标记获取 ID 和关联 Issue 数量
gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=要删除的标记' --format json
gitlink-cli label +list --owner <owner> --repo <repo> --keyword "要删除的标记" --format json
# 记录目标标记的 id 和 issues_count
# Step 2确认删除意图 如 issues_count > 0 需特别提醒)
# ⚠️ 删除标记后,关联的 Issue 将失去该标记
# Step 3删除标记
gitlink-cli api DELETE /v1/:owner/:repo/issue_tags/:id --format json
gitlink-cli label +delete --owner <owner> --repo <repo> -i <id> --dry-run
gitlink-cli label +delete --owner <owner> --repo <repo> -i <id> --yes
# Step 4验证删除结果
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
```
---
@ -553,9 +556,10 @@ gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
用户:"帮我的新仓库创建一套 Issue 标签"
AI 执行:
1. gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'only_name=true' --format json
1. gitlink-cli label +list --owner <owner> --repo <repo> --only-name true --format json
→ 确认当前标记列表为空
2. 逐个创建基础标记bug/feature/enhancement/documentation/good-first-issue/help-wanted/question/wontfix
2. gitlink-cli label +batch-create --owner <owner> --repo <repo> --labels '<基础标记集>' --dry-run
3. 用户确认后执行 gitlink-cli label +batch-create --owner <owner> --repo <repo> --labels '<基础标记集>' --yes
3. 验证创建结果
4. 输出创建报告
```
@ -566,9 +570,9 @@ AI 执行:
用户:"把 bug 标签的颜色改成红色"
AI 执行:
1. gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'keyword=bug' --format json
1. gitlink-cli label +list --owner <owner> --repo <repo> --keyword bug --format json
→ 获取 id 和当前信息
2. gitlink-cli api PATCH /v1/:owner/:repo/issue_tags/:id --body '{"name":"bug","description":"原描述","color":"#ee0701"}' --format json
2. gitlink-cli label +update --owner <owner> --repo <repo> -i <id> -c "#ee0701" --format json
3. 验证修改结果
4. 输出修改报告
```
@ -579,10 +583,11 @@ AI 执行:
用户:"删除没有关联任何 Issue 的标签"
AI 执行:
1. gitlink-cli api GET /v1/:owner/:repo/issue_tags --query 'order_by=issues_count&order_direction=asc' --format json
1. gitlink-cli label +list --owner <owner> --repo <repo> --sort-by issues_count --sort-direction asc --format json
2. AI 筛选 issues_count == 0 的标记
3. 列出待删除标记清单,请用户确认
4. 确认后逐个删除
4. 先执行 gitlink-cli label +batch-delete --owner <owner> --repo <repo> --ids <ids> --dry-run
5. 用户确认后执行 gitlink-cli label +batch-delete --owner <owner> --repo <repo> --ids <ids> --yes
5. 输出删除报告
```
@ -592,7 +597,7 @@ AI 执行:
用户:"检查有没有重复的标签"
AI 执行:
1. gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
1. gitlink-cli label +list --owner <owner> --repo <repo> --format json
2. AI 分析语义相似的标记对(如 bug/defect、feature/enhancement
3. 列出建议合并的标记对,请用户确认
4. 执行合并(迁移 Issue 标记 → 删除冗余标记)
@ -612,4 +617,3 @@ AI 执行:
- ⚠️ **标记名称唯一**:同一仓库下标记名称不能重复
- ✅ **排序参数**`sort_by` 支持 `updated_on`、`created_on`、`issues_count``sort_direction` 支持 `desc`、`asc`
- ✅ **所有操作通过 gitlink-cli api**:无需本地 git 命令,所有增删查改均通过 API 完成

View File

@ -1,7 +1,7 @@
---
name: gitlink-label
version: 1.0.0
description: "Issue label management: list, create, update, and delete GitLink issue labels (项目标记). Triggered when a user needs to manage labels, set up a triage taxonomy, or tag issues."
version: 2.0.0
description: "Issue label management: list, create, update, delete, and batch manage GitLink issue labels (项目标记). Triggered when a user needs to manage labels, set up a triage taxonomy, or tag issues."
metadata:
requires:
bins: ["gitlink-cli"]
@ -11,7 +11,7 @@ metadata:
# gitlink-label
**CRITICAL**: Read [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) before starting. It covers authentication, permissions, global flags, and GitLink API behavior.
**CRITICAL**: Confirm user intent before running write or destructive operations such as `+create`, `+update`, or `+delete`.
**CRITICAL**: Confirm user intent before running write or destructive operations such as `+create`, `+update`, `+delete`, `+batch-create`, or `+batch-delete`.
**CRITICAL**: Use `gitlink-cli` for GitLink resources. Do not use GitHub-only tools such as `gh`.
## Shortcuts
@ -21,7 +21,9 @@ metadata:
| `label +list` | List issue labels | Read |
| `label +create` | Create an issue label | Write |
| `label +update` | Update a label, preserving unspecified fields | Write |
| `label +delete` | Delete an issue label | Destructive |
| `label +delete` | Delete an issue label with dry-run/yes protection | Destructive |
| `label +batch-create` | Batch create issue labels from `name:color:description` specs | Write |
| `label +batch-delete` | Batch delete issue labels by IDs with dry-run/yes protection | Destructive |
## Examples
@ -38,8 +40,19 @@ gitlink-cli label +create --owner Gitlink --repo forgeplus -n bug -d "Something
# Update only the color; name and description are preserved
gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
# Delete a label
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
# Delete a label safely
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --dry-run
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --yes
# Batch create labels safely
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug fixes;feature:#0075ca:New features' --dry-run
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug fixes;feature:#0075ca:New features' --yes
# Batch delete labels safely
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --dry-run
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --yes
```
## Parameters
@ -49,23 +62,27 @@ gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
| `+list` | `--keyword`, `--only-name`, `--sort-by` (updated_on / created_on / issues_count), `--sort-direction` (asc / desc) |
| `+create` | `--name` (required), `--description`, `--color` (hex, default `#1E90FF`) |
| `+update` | `--id` (required) plus at least one of `--name`, `--description`, `--color` |
| `+delete` | `--id` (required) |
| `+delete` | `--id` (required), `--dry-run`, `--yes` |
| `+batch-create` | `--labels` (semicolon-separated `name:color:description` specs), `--dry-run`, `--yes` |
| `+batch-delete` | `--ids` (comma-separated label IDs), `--dry-run`, `--yes` |
## API Notes
- Labels map to the GitLink "项目标记" / `issue_tags` API: `/api/v1/{owner}/{repo}/issue_tags`.
- `--color` must be a hex value (`#RGB` or `#RRGGBB`); it is validated client-side before the API call.
- `+update` first fetches the label's current values from the list endpoint and merges the requested changes, so fields you do not pass are preserved (the API requires `name`, `description`, and `color` together).
- `+delete`, `+batch-create`, and `+batch-delete` should be run with `--dry-run` first, then repeated with `--yes` after confirmation.
- `+batch-create` defaults missing colors to `#1E90FF`; duplicate IDs in `+batch-delete` are de-duplicated client-side.
- To attach a label to an issue, pass its id via the issue update API field `issue_tag_ids` (see `gitlink-issue`); use `label +list --only-name true` to resolve label ids quickly.
## Typical workflow: bootstrap a triage taxonomy
```bash
# Create a consistent label set for issue triage
gitlink-cli label +create -n bug -c "#D73A4A" -d "Confirmed defect"
gitlink-cli label +create -n enhancement -c "#A2EEEF" -d "Feature request"
gitlink-cli label +create -n question -c "#D876E3" -d "Needs clarification"
gitlink-cli label +create -n security -c "#B60205" -d "Security-sensitive"
gitlink-cli label +batch-create --dry-run \
--labels 'bug:#D73A4A:Confirmed defect;enhancement:#A2EEEF:Feature request;question:#D876E3:Needs clarification;security:#B60205:Security-sensitive'
gitlink-cli label +batch-create --yes \
--labels 'bug:#D73A4A:Confirmed defect;enhancement:#A2EEEF:Feature request;question:#D876E3:Needs clarification;security:#B60205:Security-sensitive'
# Verify the taxonomy
gitlink-cli label +list --only-name true --format json

View File

@ -123,22 +123,17 @@ gitlink-cli api GET /v1/:owner/:repo/issues/:number/journals?category=comment&pa
```bash
# Step 1获取仓库现有标签列表
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# Step 2检查目标标签是否已存在AI 在返回结果中查找)
# 如果目标标签不存在,则创建:
# 创建"迟缓"标签30-59天
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"迟缓","description":"近期活动频率明显下降,需关注但尚未停滞","color":"#fbca04"}' --format json
# 创建"不活跃"标签60-89天
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"不活跃","description":"长期无更新或互动,可能已失去推进动力","color":"#d93f0b"}' --format json
# 创建"过期"标签≥90天
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"过期","description":"已超出合理响应周期,建议关闭或重新评估","color":"#b60205"}' --format json
# 如果目标标签不存在,则批量创建。先 dry-run再在用户确认后 --yes
gitlink-cli label +batch-create --owner <owner> --repo <repo> \
--labels '迟缓:#fbca04:近期活动频率明显下降,需关注但尚未停滞;不活跃:#d93f0b:长期无更新或互动,可能已失去推进动力;过期:#b60205:已超出合理响应周期,建议关闭或重新评估' --dry-run
gitlink-cli label +batch-create --owner <owner> --repo <repo> \
--labels '迟缓:#fbca04:近期活动频率明显下降,需关注但尚未停滞;不活跃:#d93f0b:长期无更新或互动,可能已失去推进动力;过期:#b60205:已超出合理响应周期,建议关闭或重新评估' --yes
# Step 3重新获取标签列表确认标签 ID
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
```
**标签与过期等级对应关系**
@ -428,11 +423,12 @@ gitlink-cli api GET /v1/:owner/:repo/issues/:number/journals --format json
# Step 5执行 — 用户确认后):
# a. 预创建标签(确保标签存在)
gitlink-cli api GET /v1/:owner/:repo/issue_tags --format json
gitlink-cli label +list --owner <owner> --repo <repo> --format json
# 检查迟缓/不活跃/过期标签是否存在,不存在则创建:
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"迟缓","description":"近期活动频率明显下降,需关注但尚未停滞","color":"#fbca04"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"不活跃","description":"长期无更新或互动,可能已失去推进动力","color":"#d93f0b"}' --format json
gitlink-cli api POST /v1/:owner/:repo/issue_tags --body '{"name":"过期","description":"已超出合理响应周期,建议关闭或重新评估","color":"#b60205"}' --format json
gitlink-cli label +batch-create --owner <owner> --repo <repo> \
--labels '迟缓:#fbca04:近期活动频率明显下降,需关注但尚未停滞;不活跃:#d93f0b:长期无更新或互动,可能已失去推进动力;过期:#b60205:已超出合理响应周期,建议关闭或重新评估' --dry-run
gitlink-cli label +batch-create --owner <owner> --repo <repo> \
--labels '迟缓:#fbca04:近期活动频率明显下降,需关注但尚未停滞;不活跃:#d93f0b:长期无更新或互动,可能已失去推进动力;过期:#b60205:已超出合理响应周期,建议关闭或重新评估' --yes
# b. 对 30-59 天 Issue打"迟缓"标签 + 发提醒评论
gitlink-cli api PATCH /v1/:owner/:repo/issues/:id --body '{"issue_tag_ids":[<tag_id>]}' --format json
@ -559,4 +555,4 @@ AI 应解析为:
- ✅ **关闭操作不可逆**:虽然维护者可以重新打开,但评论通知已发出,应谨慎
- ✅ **建议定期执行**:推荐每周执行一次,保持 Issue 列表健康
- ⚠️ **标签操作**:打标签通过 `gitlink-cli api PATCH /v1/:owner/:repo/issues/:id --body '{"issue_tag_ids":[<tag_id>]}'` 完成,`issue_tag_ids` 为完整替换,需包含已有标签 ID
- ⚠️ **标签预创建**:打标签前必须先查询标签列表,确认目标标签存在,不存在则先通过 `POST /v1/:owner/:repo/issue_tags` 创建
- ⚠️ **标签预创建**:打标签前必须先`gitlink-cli label +list` 查询标签列表,确认目标标签存在;不存在则先用 `gitlink-cli label +batch-create --dry-run` 预览并在确认后 `--yes` 创建