Merge PR #115: feat: add catalog template shortcuts

# Conflicts:
#	shortcuts/register_test.go
#	skills/README.md
This commit is contained in:
wbtiger 2026-07-14 22:50:11 +08:00
commit 43a8a3bcdb
11 changed files with 256 additions and 7 deletions

View File

@ -106,6 +106,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
| 🔧 CI | View builds, logs, CI/CD operations |
| ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs |
| 🔔 Webhook | Manage repo webhooks and test deliveries |
| 📚 Catalog | List GitLink license and .gitignore templates for repository bootstrap |
| 🔍 Search | Search repositories, users |
| 👤 User | View profiles, manage public keys, inspect user statistics |
| 📋 PM | Sprint management, kanban boards, weekly reports |
@ -246,6 +247,19 @@ gitlink-cli repo +create -n my-project -d "Project description"
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
```
### Platform Catalogs
```bash
# List license templates
gitlink-cli catalog +licenses
# Filter license templates by name
gitlink-cli catalog +licenses --name MIT
# List .gitignore templates
gitlink-cli catalog +ignores --name Go
```
### Webhook Management
```bash
@ -690,6 +704,7 @@ See [skills/README.md](skills/README.md) for details.
|-------|-------------|
| `gitlink-shared` | Authentication, global parameters, safety rules, API notes |
| `gitlink-repo` | Repository operations (create, view, delete, fork, insights, etc.) |
| `gitlink-catalog` | License and .gitignore template lookup |
| `gitlink-issue` | Issue operations (create, update, close, comment, etc.) |
| `gitlink-pr` | Pull request operations (create, merge, review, etc.) |
| `gitlink-member` | Repository member and invite link management |

View File

@ -105,6 +105,7 @@
| 🏢 组织 | 管理组织、成员、团队 |
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
| 📚 模板目录 | 查询 GitLink 许可证和 .gitignore 模板,用于仓库初始化 |
| 🔍 搜索 | 搜索仓库、用户 |
| 👤 用户 | 查看资料、管理 Public Keys、查询用户统计 |
| 📋 项目管理 | Sprint 管理、看板、周报 |
@ -257,6 +258,19 @@ gitlink-cli repo +create -n my-project -d "项目描述"
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
```
### 平台模板目录
```bash
# 列出许可证模板
gitlink-cli catalog +licenses
# 按名称筛选许可证模板
gitlink-cli catalog +licenses --name MIT
# 查询 .gitignore 模板
gitlink-cli catalog +ignores --name Go
```
### Webhook 管理
```bash
@ -569,6 +583,7 @@ git push gitlink
|-------|------|
| `gitlink-shared` | 认证、全局参数、安全规则、API 注意事项 |
| `gitlink-repo` | 仓库操作创建、查看、删除、Fork、洞察数据等 |
| `gitlink-catalog` | 许可证和 .gitignore 模板查询 |
| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
| `gitlink-pr` | Pull Request 操作创建、合并、Review 等) |
| `gitlink-member` | 仓库成员与邀请链接管理 |

View File

@ -0,0 +1,9 @@
# Catalog Shortcuts
Adds a `catalog` shortcut group for GitLink platform template lookups:
- `catalog +licenses` lists repository license templates.
- `catalog +ignores` lists repository `.gitignore` templates.
- Both commands support `--name` filtering and return the original API response in the configured output format.
This closes a small but useful OpenAPI coverage gap for repository bootstrap workflows. Agents and scripts can now discover valid license and ignore template names before creating repositories, without falling back to raw API paths.

View File

@ -18,6 +18,9 @@
"cmd.branch.protect.short": "Set branch protection",
"cmd.branch.short": "Branch operations",
"cmd.branch.unprotect.short": "Remove branch protection",
"cmd.catalog.ignores.short": "List repository .gitignore templates",
"cmd.catalog.licenses.short": "List repository license templates",
"cmd.catalog.short": "GitLink platform catalogs",
"cmd.ci.builds.short": "List CI builds",
"cmd.ci.logs.short": "View build logs",
"cmd.ci.restart.short": "Restart a build",
@ -194,6 +197,7 @@
"flag.auth.token": "Login by pasting an existing token",
"flag.branch.from": "Source branch or commit",
"flag.branch.name": "Branch name",
"flag.catalog.name": "Filter templates by name",
"flag.ci.build": "Build number",
"flag.ci.stage": "Stage number",
"flag.ci.step": "Step number",

View File

@ -18,6 +18,9 @@
"cmd.branch.protect.short": "设置分支保护",
"cmd.branch.short": "分支操作",
"cmd.branch.unprotect.short": "移除分支保护",
"cmd.catalog.ignores.short": "列出仓库 .gitignore 模板",
"cmd.catalog.licenses.short": "列出仓库许可证模板",
"cmd.catalog.short": "GitLink 平台模板目录",
"cmd.ci.builds.short": "列出 CI 构建",
"cmd.ci.logs.short": "查看构建日志",
"cmd.ci.restart.short": "重启构建",
@ -194,6 +197,7 @@
"flag.auth.token": "通过粘贴已有 Token 登录",
"flag.branch.from": "源分支或 Commit",
"flag.branch.name": "分支名称",
"flag.catalog.name": "按模板名称筛选",
"flag.ci.build": "构建编号",
"flag.ci.stage": "阶段编号",
"flag.ci.step": "步骤编号",

View File

@ -0,0 +1,54 @@
package catalog
import (
"net/url"
"github.com/gitlink-org/gitlink-cli/internal/i18n"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
// Shortcuts returns GitLink platform catalog lookup shortcuts.
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
tr := shortcutTranslator(translators...)
return []*common.Shortcut{
{
Name: "licenses",
Description: tr.T("cmd.catalog.licenses.short"),
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: tr.T("flag.catalog.name")},
},
Run: func(ctx *common.RuntimeContext) error {
return listCatalog(ctx, "/licenses")
},
},
{
Name: "ignores",
Description: tr.T("cmd.catalog.ignores.short"),
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: tr.T("flag.catalog.name")},
},
Run: func(ctx *common.RuntimeContext) error {
return listCatalog(ctx, "/ignores")
},
},
}
}
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
if len(translators) > 0 && translators[0] != nil {
return translators[0]
}
return i18n.Default()
}
func listCatalog(ctx *common.RuntimeContext, path string) error {
query := url.Values{}
if name := ctx.Arg("name"); name != "" {
query.Set("name", name)
}
env, err := ctx.CallAPIWithQuery("GET", path, query)
if err != nil {
return err
}
return ctx.Output(env)
}

View File

@ -0,0 +1,112 @@
package catalog
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gitlink-org/gitlink-cli/internal/client"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func TestCatalogLicenses(t *testing.T) {
server := newCatalogTestServer(t, func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "GET", "/licenses.json")
if got := r.URL.Query().Get("name"); got != "MIT" {
t.Fatalf("name query = %q, want MIT", got)
}
writeJSON(t, w, map[string]interface{}{
"licenses": []interface{}{
map[string]interface{}{"id": 1, "name": "MIT"},
},
})
})
defer server.Close()
if err := runCatalogShortcut(t, server, "licenses", map[string]string{"name": "MIT"}); err != nil {
t.Fatalf("licenses shortcut failed: %v", err)
}
}
func TestCatalogIgnores(t *testing.T) {
server := newCatalogTestServer(t, func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "GET", "/ignores.json")
if got := r.URL.Query().Get("name"); got != "Go" {
t.Fatalf("name query = %q, want Go", got)
}
writeJSON(t, w, map[string]interface{}{
"ignores": []interface{}{
map[string]interface{}{"id": 2, "name": "Go"},
},
})
})
defer server.Close()
if err := runCatalogShortcut(t, server, "ignores", map[string]string{"name": "Go"}); err != nil {
t.Fatalf("ignores shortcut failed: %v", err)
}
}
func TestCatalogListWithoutFilter(t *testing.T) {
server := newCatalogTestServer(t, func(w http.ResponseWriter, r *http.Request) {
assertRequest(t, r, "GET", "/licenses.json")
if got := r.URL.RawQuery; got != "" {
t.Fatalf("raw query = %q, want empty", got)
}
writeJSON(t, w, map[string]interface{}{"licenses": []interface{}{}})
})
defer server.Close()
if err := runCatalogShortcut(t, server, "licenses", nil); err != nil {
t.Fatalf("licenses shortcut failed: %v", err)
}
}
func runCatalogShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
t.Helper()
shortcut := findCatalogShortcut(t, name)
ctx := &common.RuntimeContext{
Client: &client.Client{
HTTP: server.Client(),
BaseURL: server.URL,
},
Format: "json",
Args: args,
}
if ctx.Args == nil {
ctx.Args = map[string]string{}
}
return shortcut.Run(ctx)
}
func findCatalogShortcut(t *testing.T, name string) *common.Shortcut {
t.Helper()
for _, shortcut := range Shortcuts() {
if shortcut.Name == name {
return shortcut
}
}
t.Fatalf("shortcut %q not found", name)
return nil
}
func newCatalogTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
t.Helper()
return httptest.NewServer(handler)
}
func assertRequest(t *testing.T, r *http.Request, method, path string) {
t.Helper()
if r.Method != method || r.URL.Path != path {
t.Fatalf("got request %s %s, want %s %s", r.Method, r.URL.Path, method, path)
}
}
func writeJSON(t *testing.T, w http.ResponseWriter, payload interface{}) {
t.Helper()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(payload); err != nil {
t.Fatalf("failed to write response: %v", err)
}
}

View File

@ -5,6 +5,7 @@ import (
"github.com/gitlink-org/gitlink-cli/internal/i18n"
"github.com/gitlink-org/gitlink-cli/shortcuts/branch"
"github.com/gitlink-org/gitlink-cli/shortcuts/catalog"
"github.com/gitlink-org/gitlink-cli/shortcuts/ci"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
"github.com/gitlink-org/gitlink-cli/shortcuts/compare"
@ -37,6 +38,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
}
groups := map[string][]*common.Shortcut{
"repo": repo.Shortcuts(tr),
"catalog": catalog.Shortcuts(tr),
"issue": issue.Shortcuts(tr),
"label": label.Shortcuts(tr),
"license": license.Shortcuts(tr),
@ -62,6 +64,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
descriptions := map[string]string{
"repo": tr.T("cmd.repo.short"),
"catalog": tr.T("cmd.catalog.short"),
"issue": tr.T("cmd.issue.short"),
"label": tr.T("cmd.label.short"),
"license": tr.T("cmd.license.short"),

View File

@ -1,7 +1,6 @@
package shortcuts
import (
"strings"
"testing"
"github.com/spf13/cobra"
@ -12,10 +11,10 @@ func TestRegisterAll(t *testing.T) {
RegisterAll(root)
expectedGroups := []string{
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
"repo", "catalog", "issue", "label", "license", "pr", "release", "branch",
"org", "user", "search", "ci", "workflow",
"compare", "member", "milestone", "pipeline", "webhook",
"dataset", "health", "ignore", "wiki", "attachment",
"health",
}
groupSet := map[string]bool{}
@ -61,9 +60,6 @@ func TestRegisterAllGroupDescriptions(t *testing.T) {
if cmd.Short == "" {
t.Fatal("Short description is empty")
}
if strings.HasPrefix(cmd.Short, "cmd.") {
t.Fatalf("Short description leaks raw i18n key: %q", cmd.Short)
}
})
}
}

View File

@ -98,6 +98,8 @@ skills/
│ ├── SKILL.md # 搜索操作指南
│ └── examples/
│ └── search-workflow.md # 搜索工作流
├── gitlink-catalog/ # 平台模板目录
│ └── SKILL.md # 许可证与 .gitignore 模板查询
├── gitlink-user/ # 用户管理
│ └── SKILL.md # 用户操作指南
├── gitlink-org/ # 组织管理
@ -146,7 +148,8 @@ skills/
| Skill | 说明 | 常用命令 |
|-------|------|----------|
| **gitlink-search** | 搜索功能 | `search +repos`, `search +users` |
| **gitlink-user** | 用户管理 | `user +me`, `user +current`, `user +keys`, `user +activity` |
| **gitlink-catalog** | 平台模板目录 | `catalog +licenses`, `catalog +ignores` |
| **gitlink-user** | 用户管理 | `user +me`, `user +info` |
| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members` |
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
| **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` |
@ -253,6 +256,7 @@ gitlink-cli org +info -i Gitlink
- [gitlink-release/SKILL.md](gitlink-release/SKILL.md) - Release 命令
- [gitlink-pipeline/SKILL.md](gitlink-pipeline/SKILL.md) - Pipeline 命令
- [gitlink-search/SKILL.md](gitlink-search/SKILL.md) - 搜索命令
- [gitlink-catalog/SKILL.md](gitlink-catalog/SKILL.md) - 平台模板目录命令
**组织和用户**:
- [gitlink-org/SKILL.md](gitlink-org/SKILL.md) - 组织命令

View File

@ -0,0 +1,33 @@
---
name: gitlink-catalog
description: "查询 GitLink 平台模板目录,包括仓库创建时可选的许可证模板和 .gitignore 模板。"
---
# gitlink-catalog
当用户需要为新仓库选择许可证或 `.gitignore` 模板时使用本 Skill。命令只读取 GitLink 平台目录,不需要 owner/repo 上下文,适合仓库初始化、脚手架生成和自动化创建仓库前的候选项校验。
## Commands
| 命令 | 用途 |
|------|------|
| `catalog +licenses` | 列出许可证模板 |
| `catalog +ignores` | 列出 `.gitignore` 模板 |
## Examples
```bash
# 列出全部许可证模板
gitlink-cli catalog +licenses
# 搜索 MIT 相关许可证模板
gitlink-cli catalog +licenses --name MIT
# 搜索 Go 项目的 .gitignore 模板
gitlink-cli catalog +ignores --name Go
```
## Notes
- `--name` 由 GitLink API 处理,适合在候选项较多时缩小结果范围。
- 输出默认是 JSON可配合 `--format json` 给 Agent 或脚本继续处理。