From 906bdb9d6d6f583cd17c80e2f0dd35f2c05a0f58 Mon Sep 17 00:00:00 2001 From: NeeNe <26158277@qq.com> Date: Fri, 3 Jul 2026 10:17:42 +0800 Subject: [PATCH] feat(org): add organization team shortcuts --- README.md | 18 +++- README.zh-CN.md | 18 +++- doc/changes/org-team-shortcuts.md | 33 +++++++ internal/i18n/locales/en-US.json | 1 + internal/i18n/locales/zh-CN.json | 1 + shortcuts/org/org.go | 38 +++++++- shortcuts/org/org_test.go | 86 +++++++++++++++++++ skills/README.md | 3 +- skills/gitlink-org/SKILL.md | 7 +- .../references/gitlink-org-teams.md | 50 +++++++++++ 10 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 doc/changes/org-team-shortcuts.md create mode 100644 skills/gitlink-org/references/gitlink-org-teams.md diff --git a/README.md b/README.md index e5e4318..2e31aab 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans | 👥 Member | List, add, remove repository members, change roles, create and accept invite links | | 🌿 Branch | Create, delete, list, protect, unprotect branches | | 🏷️ Release | Create, edit, update, view, delete releases | -| 🏢 Org | Manage organizations, members, teams | +| 🏢 Org | Manage organizations, list members, and inspect teams | | 🔧 CI | View builds, logs, CI/CD operations | | ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs | | 🔔 Webhook | Manage repo webhooks and test deliveries | @@ -318,6 +318,22 @@ gitlink-cli member +role --owner Gitlink --repo forgeplus --user-id 101 --role D gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role developer --apply true ``` +### Organization Management + +```bash +# List organizations +gitlink-cli org +list + +# View organization details +gitlink-cli org +info --id Gitlink + +# List organization members +gitlink-cli org +members --id Gitlink --page 1 --limit 20 + +# List organization teams +gitlink-cli org +teams --id Gitlink --page 1 --limit 20 +``` + ### Issue Management ```bash diff --git a/README.zh-CN.md b/README.zh-CN.md index 6a8879d..7f9769c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -110,7 +110,7 @@ | 👥 成员 | 列出、添加、移除仓库成员,调整角色,生成和接受邀请链接 | | 🌿 分支 | 创建、删除、保护分支 | | 🏷️ 发布 | 创建、编辑、更新、查看、删除 Release | -| 🏢 组织 | 管理组织、成员、团队 | +| 🏢 组织 | 管理组织、列出成员、查看团队 | | 🔧 CI | 查看构建、日志、CI/CD 操作 | | ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 | | 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 | @@ -329,6 +329,22 @@ gitlink-cli member +role --owner Gitlink --repo forgeplus --user-id 101 --role D gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role developer --apply true ``` +### 组织管理 + +```bash +# 列出组织 +gitlink-cli org +list + +# 查看组织详情 +gitlink-cli org +info --id Gitlink + +# 列出组织成员 +gitlink-cli org +members --id Gitlink --page 1 --limit 20 + +# 列出组织团队 +gitlink-cli org +teams --id Gitlink --page 1 --limit 20 +``` + ### Issue 管理 ```bash diff --git a/doc/changes/org-team-shortcuts.md b/doc/changes/org-team-shortcuts.md new file mode 100644 index 0000000..05e3c78 --- /dev/null +++ b/doc/changes/org-team-shortcuts.md @@ -0,0 +1,33 @@ +# Organization Team Shortcuts + +## Summary + +Adds a read-only organization team shortcut so users and agents can inspect teams without dropping down to Raw API calls. + +## Commands + +```bash +gitlink-cli org +teams --id Gitlink +gitlink-cli org +teams --id Gitlink --page 1 --limit 50 +gitlink-cli org +teams --id Gitlink --format json +``` + +## Behavior + +- Calls `GET /organizations/{id}/teams`. +- Supports `--page` and `--limit`, matching `org +list` and `org +members` pagination behavior. +- Requires `--id` to avoid accidental ambiguous organization lookup. +- Leaves team creation and destructive team management in Raw API because only the read-only list endpoint is currently documented by the GitLink org Skill. + +## Documentation + +- Updates the `gitlink-org` Skill index to include `org +teams`. +- Adds a dedicated `gitlink-org-teams` reference page for Agent usage. +- Updates README feature wording to state that organization teams can be inspected through shortcuts. + +## Verification + +```bash +go test ./shortcuts/org ./shortcuts +go test ./... +``` diff --git a/internal/i18n/locales/en-US.json b/internal/i18n/locales/en-US.json index 0739395..ed364d9 100644 --- a/internal/i18n/locales/en-US.json +++ b/internal/i18n/locales/en-US.json @@ -52,6 +52,7 @@ "cmd.org.list.short": "List organizations", "cmd.org.members.short": "List organization members", "cmd.org.short": "Organization operations", + "cmd.org.teams.short": "List organization teams", "cmd.pr.close.short": "Close a pull request", "cmd.pr.comment.short": "Add a comment to a pull request", "cmd.pr.create.short": "Create a pull request", diff --git a/internal/i18n/locales/zh-CN.json b/internal/i18n/locales/zh-CN.json index 2e6fc4d..396fd97 100644 --- a/internal/i18n/locales/zh-CN.json +++ b/internal/i18n/locales/zh-CN.json @@ -52,6 +52,7 @@ "cmd.org.list.short": "列出组织", "cmd.org.members.short": "列出组织成员", "cmd.org.short": "组织操作", + "cmd.org.teams.short": "列出组织团队", "cmd.pr.close.short": "关闭拉取请求", "cmd.pr.comment.short": "给拉取请求添加评论", "cmd.pr.create.short": "创建拉取请求", diff --git a/shortcuts/org/org.go b/shortcuts/org/org.go index f0b5e72..645700f 100644 --- a/shortcuts/org/org.go +++ b/shortcuts/org/org.go @@ -36,7 +36,10 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { {Name: "id", Short: "i", Usage: tr.T("flag.org.id_or_login"), Required: true}, }, Run: func(ctx *common.RuntimeContext) error { - id, _ := ctx.RequireArg("id") + id, err := ctx.RequireArg("id") + if err != nil { + return err + } env, err := ctx.CallAPI("GET", fmt.Sprintf("/organizations/%s", id), nil) if err != nil { return err @@ -53,7 +56,10 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { {Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"}, }, Run: func(ctx *common.RuntimeContext) error { - id, _ := ctx.RequireArg("id") + id, err := ctx.RequireArg("id") + if err != nil { + return err + } q := url.Values{} q.Set("page", ctx.Arg("page")) q.Set("limit", ctx.Arg("limit")) @@ -64,6 +70,29 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "teams", + Description: tr.T("cmd.org.teams.short"), + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: tr.T("flag.org.id"), Required: true}, + {Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"}, + {Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"}, + }, + Run: func(ctx *common.RuntimeContext) error { + id, err := ctx.RequireArg("id") + if err != nil { + return err + } + q := url.Values{} + q.Set("page", ctx.Arg("page")) + q.Set("limit", ctx.Arg("limit")) + env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/organizations/%s/teams", id), q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, { Name: "create", Description: tr.T("cmd.org.create.short"), @@ -72,7 +101,10 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { {Name: "description", Short: "d", Usage: tr.T("flag.description")}, }, Run: func(ctx *common.RuntimeContext) error { - name, _ := ctx.RequireArg("name") + name, err := ctx.RequireArg("name") + if err != nil { + return err + } payload := map[string]interface{}{ "name": name, } diff --git a/shortcuts/org/org_test.go b/shortcuts/org/org_test.go index 067e4a2..fc03085 100644 --- a/shortcuts/org/org_test.go +++ b/shortcuts/org/org_test.go @@ -76,6 +76,18 @@ func TestOrgInfo(t *testing.T) { } } +func TestOrgInfoRequiresID(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected API request: %s %s", r.Method, r.URL.String()) + })) + defer server.Close() + + err := runShortcut(t, server, "info", map[string]string{}) + if err == nil { + t.Fatal("expected error for missing organization id") + } +} + // --- members --- func TestOrgMembers(t *testing.T) { @@ -95,6 +107,55 @@ func TestOrgMembers(t *testing.T) { } } +func TestOrgMembersRequiresID(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected API request: %s %s", r.Method, r.URL.String()) + })) + defer server.Close() + + err := runShortcut(t, server, "members", map[string]string{"page": "1", "limit": "20"}) + if err == nil { + t.Fatal("expected error for missing organization id") + } +} + +// --- teams --- + +func TestOrgTeams(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/organizations/myorg/teams.json" { + t.Fatalf("unexpected path: %s", r.URL.Path) + } + if got := r.URL.Query().Get("page"); got != "2" { + t.Fatalf("page query = %q, want 2", got) + } + if got := r.URL.Query().Get("limit"); got != "50" { + t.Fatalf("limit query = %q, want 50", got) + } + writeJSON(w, []interface{}{ + map[string]interface{}{"id": 1, "name": "maintainers"}, + }) + })) + defer server.Close() + + err := runShortcut(t, server, "teams", map[string]string{"id": "myorg", "page": "2", "limit": "50"}) + if err != nil { + t.Fatalf("teams failed: %v", err) + } +} + +func TestOrgTeamsRequiresID(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected API request: %s %s", r.Method, r.URL.String()) + })) + defer server.Close() + + err := runShortcut(t, server, "teams", map[string]string{"page": "1", "limit": "20"}) + if err == nil { + t.Fatal("expected error for missing organization id") + } +} + // --- create --- func TestOrgCreate(t *testing.T) { @@ -127,6 +188,18 @@ func TestOrgCreateNoDescription(t *testing.T) { } } +func TestOrgCreateRequiresName(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected API request: %s %s", r.Method, r.URL.String()) + })) + defer server.Close() + + err := runShortcut(t, server, "create", map[string]string{}) + if err == nil { + t.Fatal("expected error for missing organization name") + } +} + // --- HTTP error paths --- func TestOrgListHTTPError(t *testing.T) { @@ -168,6 +241,19 @@ func TestOrgMembersHTTPError(t *testing.T) { } } +func TestOrgTeamsHTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("server error")) + })) + defer server.Close() + + err := runShortcut(t, server, "teams", map[string]string{"id": "myorg", "page": "1", "limit": "20"}) + if err == nil { + t.Fatal("expected error for HTTP 500") + } +} + func TestOrgCreateHTTPError(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) diff --git a/skills/README.md b/skills/README.md index d507074..95e2694 100644 --- a/skills/README.md +++ b/skills/README.md @@ -140,7 +140,7 @@ skills/ |-------|------|----------| | **gitlink-search** | 搜索功能 | `search +repos`, `search +users` | | **gitlink-user** | 用户管理 | `user +me`, `user +info` | -| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members` | +| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members`, `org +teams` | | **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` | | **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` | | **gitlink-wiki** | Wiki 页面管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` | @@ -216,6 +216,7 @@ gitlink-cli search +users -k "zhangsan" # 查看组织 gitlink-cli org +list gitlink-cli org +info -i Gitlink +gitlink-cli org +teams -i Gitlink ``` 详见: [gitlink-search/SKILL.md](gitlink-search/SKILL.md) diff --git a/skills/gitlink-org/SKILL.md b/skills/gitlink-org/SKILL.md index aef61a9..fde59ac 100644 --- a/skills/gitlink-org/SKILL.md +++ b/skills/gitlink-org/SKILL.md @@ -1,7 +1,7 @@ --- name: gitlink-org version: 1.0.0 -description: "组织管理:查看组织列表、详情、成员,创建组织。当用户需要操作 GitLink 组织时触发。" +description: "组织管理:查看组织列表、详情、成员、团队,创建组织。当用户需要操作 GitLink 组织时触发。" metadata: requires: bins: ["gitlink-cli"] @@ -23,6 +23,7 @@ metadata: | `org +list` | 组织列表 | | `org +info` | 组织详情 | | `org +members` | 成员列表 | +| `org +teams` | 团队列表 | | `org +create` | 创建组织 | ## 使用示例 @@ -31,14 +32,14 @@ metadata: gitlink-cli org +list gitlink-cli org +info --id Gitlink gitlink-cli org +members --id Gitlink +gitlink-cli org +teams --id Gitlink --page 1 --limit 20 gitlink-cli org +create --name my-org --description "我的组织" ``` ## Raw API 补充 ```bash -# 组织团队管理 -gitlink-cli api GET /organizations/:id/teams +# 创建组织团队 gitlink-cli api POST /organizations/:id/teams --body '{"name":"dev-team"}' # 移除成员 diff --git a/skills/gitlink-org/references/gitlink-org-teams.md b/skills/gitlink-org/references/gitlink-org-teams.md new file mode 100644 index 0000000..fbbfd12 --- /dev/null +++ b/skills/gitlink-org/references/gitlink-org-teams.md @@ -0,0 +1,50 @@ +# org +teams + +> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。 + +列出指定组织下的团队。 + +## 命令 + +```bash +# 列出组织团队 +gitlink-cli org +teams --id Gitlink + +# 分页 +gitlink-cli org +teams --id Gitlink --page 1 --limit 50 + +# JSON 格式输出,便于 Agent 解析 +gitlink-cli org +teams --id Gitlink --format json +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--id` / `-i` | 是 | 组织标识(login name 或 ID) | +| `--page` / `-p` | 否 | 页码(默认 1) | +| `--limit` / `-l` | 否 | 每页数量(默认 20) | +| `--format` | 否 | 输出格式:json / table / yaml | + +## 输出字段 + +返回组织团队列表,字段取决于 GitLink API 返回结构,常见字段包括: + +| 字段 | 说明 | +|------|------| +| `id` | 团队 ID | +| `name` | 团队名称 | +| `description` | 团队描述 | +| `members_count` | 团队成员数量 | +| `projects_count` | 团队项目数量 | + +## 注意事项 + +- 该命令是只读操作,不会修改组织团队配置。 +- 创建团队、删除团队或批量调整团队项目仍需使用 Raw API,执行写入/删除前必须确认用户意图。 +- 如果需要查看团队成员,请先确认 GitLink API 是否公开对应团队成员端点,再使用 Raw API。 + +## References + +- [gitlink-org](../SKILL.md) +- [gitlink-shared](../../gitlink-shared/SKILL.md) -- 2.34.1