feat: add catalog template shortcuts

This commit is contained in:
Mengz 2026-06-04 21:40:37 +08:00
parent 52b7093846
commit 4fad5a32f3
11 changed files with 270 additions and 17 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 user profiles and info |
| 📋 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
@ -638,6 +652,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 模板,用于仓库初始化 |
| 🔍 搜索 | 搜索仓库、用户 |
| 👤 用户 | 查看用户资料和信息 |
| 📋 项目管理 | 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
@ -517,6 +531,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

@ -11,6 +11,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",
@ -90,18 +93,19 @@
"error.config.save_failed": "failed to save config: {message}",
"error.missing_required_flag": "required flag --{name} is missing",
"error.unsupported_language": "unsupported language: {lang}",
"flag.api.body": "Request body (JSON string)",
"flag.api.body_file": "Read request body JSON from a file",
"flag.api.body_stdin": "Read request body JSON from stdin",
"flag.api.batch_continue_on_error": "Continue running remaining batch requests after a failure",
"flag.api.batch_dry_run": "Preview batch requests without sending remote requests",
"flag.api.batch_file": "Read an API batch plan from a JSON file",
"flag.api.batch_var": "Override a batch template variable (key=value, repeatable)",
"flag.api.body": "Request body (JSON string)",
"flag.api.body_file": "Read request body JSON from a file",
"flag.api.body_stdin": "Read request body JSON from stdin",
"flag.api.header": "Additional headers (key:value)",
"flag.api.query": "Query parameters (key=val&key2=val2)",
"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",
@ -113,6 +117,8 @@
"flag.format": "Output format: json, table, yaml (default: table)",
"flag.issue.add_label": "Label to add to each matching issue",
"flag.issue.assignee": "Assignee login",
"flag.issue.assignee_id": "Assignee user ID",
"flag.issue.author_id": "Author user ID",
"flag.issue.batch.reason": "Optional reason shown in the batch result",
"flag.issue.batch.yes": "Execute remote operations. Without this flag the command is dry-run only.",
"flag.issue.batch_close.older_than_days": "Required safety filter; must be at least 7",
@ -120,8 +126,6 @@
"flag.issue.batch_label.state": "Filter by issue state",
"flag.issue.batch_list.limit": "Maximum issues to return, capped at 100",
"flag.issue.batch_process.limit": "Maximum issues to process, capped at 100",
"flag.issue.assignee_id": "Assignee user ID",
"flag.issue.author_id": "Author user ID",
"flag.issue.body": "Issue description",
"flag.issue.label": "Label ID",
"flag.issue.label_filter": "Filter by existing label",
@ -149,14 +153,14 @@
"flag.pr.file": "Filter diff by file path",
"flag.pr.head": "Source branch",
"flag.pr.id": "PR number",
"flag.pr.milestone_id": "Milestone ID",
"flag.pr.merge_method": "Merge method: merge, rebase, squash",
"flag.pr.milestone_id": "Milestone ID",
"flag.pr.priority_id": "Priority ID",
"flag.pr.review_commit": "Commit SHA to attach the review to",
"flag.pr.review_content": "Review content",
"flag.pr.reviewer_id": "Reviewer user ID",
"flag.pr.review_status": "Review status: common, approved, rejected",
"flag.pr.review_status_filter": "Filter review status: common, approved, rejected",
"flag.pr.reviewer_id": "Reviewer user ID",
"flag.pr.state": "Filter: open, merged, closed",
"flag.pr.tag_id": "Issue tag ID",
"flag.pr.title": "PR title",
@ -194,7 +198,6 @@
"output.auth.login_hint": " Run: gitlink-cli auth login",
"output.config.file": "Config file: {path}",
"output.config.not_set": "(not set)",
"output.version": "gitlink-cli {version}",
"output.doctor.api_auth.config_skipped": "API authentication check skipped because the configuration file is invalid.",
"output.doctor.api_auth.failed": "Authenticated API request failed: {message}",
"output.doctor.api_auth.no_login": "Authenticated API response did not include a login field.",
@ -217,6 +220,7 @@
"output.doctor.suggestion.check_token": "Check whether the stored token is valid, or run gitlink-cli auth login again.",
"output.doctor.suggestion.fix_config_yaml": "Fix the YAML syntax in the gitlink-cli config file.",
"output.doctor.suggestion.pass_owner_repo": "Run the command with --owner and --repo when not inside a GitLink repository.",
"output.version": "gitlink-cli {version}",
"prompt.auth.password": "Password: ",
"prompt.auth.token": "Paste your access token: ",
"prompt.auth.username": "Username/Email/Phone: ",

View File

@ -11,6 +11,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": "重启构建",
@ -90,18 +93,19 @@
"error.config.save_failed": "保存配置失败:{message}",
"error.missing_required_flag": "缺少必需参数 --{name}",
"error.unsupported_language": "不支持的语言:{lang}",
"flag.api.body": "请求体JSON 字符串)",
"flag.api.body_file": "从文件读取 JSON 请求体",
"flag.api.body_stdin": "从标准输入读取 JSON 请求体",
"flag.api.batch_continue_on_error": "批处理请求失败后继续执行后续请求",
"flag.api.batch_dry_run": "预览批处理请求,不发送远端请求",
"flag.api.batch_file": "从 JSON 文件读取 API 批处理计划",
"flag.api.batch_var": "覆盖批处理模板变量key=value可重复",
"flag.api.body": "请求体JSON 字符串)",
"flag.api.body_file": "从文件读取 JSON 请求体",
"flag.api.body_stdin": "从标准输入读取 JSON 请求体",
"flag.api.header": "附加请求头key:value",
"flag.api.query": "查询参数key=val&key2=val2",
"flag.auth.token": "通过粘贴已有 Token 登录",
"flag.branch.from": "源分支或 Commit",
"flag.branch.name": "分支名称",
"flag.catalog.name": "按模板名称筛选",
"flag.ci.build": "构建编号",
"flag.ci.stage": "阶段编号",
"flag.ci.step": "步骤编号",
@ -113,6 +117,8 @@
"flag.format": "输出格式json、table、yaml默认table",
"flag.issue.add_label": "要添加到每个匹配议题的标签",
"flag.issue.assignee": "负责人登录名",
"flag.issue.assignee_id": "负责人用户 ID",
"flag.issue.author_id": "作者用户 ID",
"flag.issue.batch.reason": "批量结果中显示的可选原因",
"flag.issue.batch.yes": "执行远端操作。未传入该参数时仅 dry-run。",
"flag.issue.batch_close.older_than_days": "必需的安全筛选条件;至少为 7",
@ -120,8 +126,6 @@
"flag.issue.batch_label.state": "按议题状态筛选",
"flag.issue.batch_list.limit": "最多返回的议题数,上限 100",
"flag.issue.batch_process.limit": "最多处理的议题数,上限 100",
"flag.issue.assignee_id": "负责人用户 ID",
"flag.issue.author_id": "作者用户 ID",
"flag.issue.body": "议题描述",
"flag.issue.label": "标签 ID",
"flag.issue.label_filter": "按已有标签筛选",
@ -149,14 +153,14 @@
"flag.pr.file": "按文件路径筛选 diff",
"flag.pr.head": "源分支",
"flag.pr.id": "PR 编号",
"flag.pr.milestone_id": "里程碑 ID",
"flag.pr.merge_method": "合并方式merge、rebase、squash",
"flag.pr.milestone_id": "里程碑 ID",
"flag.pr.priority_id": "优先级 ID",
"flag.pr.review_commit": "关联评审的 Commit SHA",
"flag.pr.review_content": "评审内容",
"flag.pr.reviewer_id": "评审人用户 ID",
"flag.pr.review_status": "评审状态common、approved、rejected",
"flag.pr.review_status_filter": "按评审状态筛选common、approved、rejected",
"flag.pr.reviewer_id": "评审人用户 ID",
"flag.pr.state": "筛选open、merged、closed",
"flag.pr.tag_id": "议题标签 ID",
"flag.pr.title": "PR 标题",
@ -194,7 +198,6 @@
"output.auth.login_hint": " 运行gitlink-cli auth login",
"output.config.file": "配置文件:{path}",
"output.config.not_set": "(未设置)",
"output.version": "gitlink-cli {version}",
"output.doctor.api_auth.config_skipped": "配置文件无效,已跳过 API 认证检查。",
"output.doctor.api_auth.failed": "认证 API 请求失败:{message}",
"output.doctor.api_auth.no_login": "认证 API 响应中缺少 login 字段。",
@ -217,6 +220,7 @@
"output.doctor.suggestion.check_token": "检查已保存的 Token 是否有效,或重新运行 gitlink-cli auth login。",
"output.doctor.suggestion.fix_config_yaml": "修复 gitlink-cli 配置文件中的 YAML 语法。",
"output.doctor.suggestion.pass_owner_repo": "不在 GitLink 仓库目录内时,请通过 --owner 和 --repo 指定仓库。",
"output.version": "gitlink-cli {version}",
"prompt.auth.password": "密码:",
"prompt.auth.token": "粘贴你的访问 Token",
"prompt.auth.username": "用户名/邮箱/手机号:",

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"
@ -33,6 +34,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(),
"license": license.Shortcuts(),
@ -54,6 +56,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": "Issue label operations",
"license": "License operations",

View File

@ -11,7 +11,7 @@ func TestRegisterAll(t *testing.T) {
RegisterAll(root)
expectedGroups := []string{
"repo", "issue", "label", "license", "pr", "release", "branch",
"repo", "catalog", "issue", "label", "license", "pr", "release", "branch",
"org", "user", "search", "ci", "workflow",
"compare", "member", "milestone", "pipeline", "webhook",
"health",

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,6 +148,7 @@ skills/
| Skill | 说明 | 常用命令 |
|-------|------|----------|
| **gitlink-search** | 搜索功能 | `search +repos`, `search +users` |
| **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` |
@ -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 或脚本继续处理。