diff --git a/README.md b/README.md index e5e4318..0c1f208 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,11 @@ gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main +# Read raw files and common dependency manifests +gitlink-cli repo +raw --owner Gitlink --repo forgeplus --path LICENSE --ref master +gitlink-cli repo +file-exists --owner Gitlink --repo forgeplus --path package.json --ref master +gitlink-cli repo +manifest --owner Gitlink --repo forgeplus --kind go --ref master + # Show language breakdown gitlink-cli repo +languages --owner Gitlink --repo forgeplus diff --git a/README.zh-CN.md b/README.zh-CN.md index 6a8879d..6ebb05b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -238,6 +238,11 @@ gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main +# 读取原始文件和常见依赖清单 +gitlink-cli repo +raw --owner Gitlink --repo forgeplus --path LICENSE --ref master +gitlink-cli repo +file-exists --owner Gitlink --repo forgeplus --path package.json --ref master +gitlink-cli repo +manifest --owner Gitlink --repo forgeplus --kind go --ref master + # 查看语言占比 gitlink-cli repo +languages --owner Gitlink --repo forgeplus diff --git a/doc/changes/repo-raw-file-shortcuts.md b/doc/changes/repo-raw-file-shortcuts.md new file mode 100644 index 0000000..df8ed3d --- /dev/null +++ b/doc/changes/repo-raw-file-shortcuts.md @@ -0,0 +1,75 @@ +# Repository raw file shortcuts + +## Background + +Repository inspection workflows often need to read files such as `LICENSE`, +`README.md`, dependency manifests, and source files. Those workflows previously +had to use Raw API calls for `/raw//` and `sub_entries`. + +This change adds first-class read-only shortcuts for raw file content and common +manifest discovery. + +## New shortcuts + +- `repo +raw` reads raw file content at a branch, tag, or commit ref. +- `repo +file-exists` checks whether a repository file exists without returning + the full content. +- `repo +manifest` reads common dependency manifests for `go`, `node`, + `python`, `rust`, and `java`. + +## Examples + +```bash +gitlink-cli repo +raw --owner Gitlink --repo forgeplus --path LICENSE --ref master +gitlink-cli repo +file-exists --owner Gitlink --repo forgeplus --path package.json --ref master +gitlink-cli repo +manifest --owner Gitlink --repo forgeplus --kind go --ref master +``` + +## Manifest mapping + +| Kind | Candidate files | +|------|-----------------| +| `go` | `go.mod` | +| `node` | `package.json` | +| `python` | `requirements.txt`, `pyproject.toml`, `Pipfile`, `setup.py` | +| `rust` | `Cargo.toml` | +| `java` | `pom.xml`, `build.gradle`, `build.gradle.kts` | + +## Safety and path handling + +- These shortcuts are read-only. +- `--ref` defaults to `master`. +- File paths are cleaned of leading/trailing slashes. +- Parent path segments (`..`) are rejected before making API requests. +- Raw paths are escaped segment by segment for paths containing spaces or other + special characters. + +## Documentation updates + +- README and README.zh-CN include raw file, file existence, and manifest examples. +- `skills/gitlink-repo` documents the new shortcuts. +- `skills/gitlink-compliance` and `skills/gitlink-code-review` now prefer + shortcuts over Raw API calls for file tree and content reads. + +## Tests + +Unit tests cover: + +- raw file path construction and default refs; +- file existence checks through `sub_entries`; +- manifest kind mapping and fallback behavior; +- missing manifests; +- invalid paths and unknown manifest kinds; +- HTTP error propagation. + +Suggested verification: + +```bash +go test ./shortcuts/repo ./shortcuts +``` + +Full project verification: + +```bash +go test ./... +``` diff --git a/internal/i18n/locales/en-US.json b/internal/i18n/locales/en-US.json index 0739395..07a48d5 100644 --- a/internal/i18n/locales/en-US.json +++ b/internal/i18n/locales/en-US.json @@ -83,9 +83,12 @@ "cmd.release.view.short": "View release details", "cmd.repo.create.short": "Create a new repository", "cmd.repo.delete.short": "Delete a repository", + "cmd.repo.file_exists.short": "Check whether a repository file exists", "cmd.repo.fork.short": "Fork a repository", "cmd.repo.info.short": "Show repository details", "cmd.repo.list.short": "List repositories for a user or organization", + "cmd.repo.manifest.short": "Read a common dependency manifest", + "cmd.repo.raw.short": "Read raw repository file content", "cmd.repo.short": "Repository operations", "cmd.repo.tree.short": "List repository files and directories", "cmd.root.long": "Manage repositories, issues, pull requests, releases, CI and workflows on GitLink.", @@ -210,8 +213,12 @@ "flag.repo": "Repository name (auto-detected from git remote)", "flag.repo.category": "Filter: manage/mirror/sync/fork/all (default: manage)", "flag.repo.description": "Repository description", + "flag.repo.file_exists.path": "File path to check", + "flag.repo.manifest.kind": "Manifest kind: go, node, python, rust, or java", "flag.repo.name": "Repository name", "flag.repo.private": "Make repository private (true/false)", + "flag.repo.raw.path": "File path in the repository", + "flag.repo.raw.ref": "Branch, tag, or commit ref", "flag.repo.tree.path": "Directory path to list (default: repository root)", "flag.repo.tree.ref": "Branch, tag, or commit ref", "flag.search.keyword": "Search keyword", diff --git a/internal/i18n/locales/zh-CN.json b/internal/i18n/locales/zh-CN.json index 2e6fc4d..720d58d 100644 --- a/internal/i18n/locales/zh-CN.json +++ b/internal/i18n/locales/zh-CN.json @@ -83,9 +83,12 @@ "cmd.release.view.short": "查看发布详情", "cmd.repo.create.short": "创建新仓库", "cmd.repo.delete.short": "删除仓库", + "cmd.repo.file_exists.short": "检查仓库文件是否存在", "cmd.repo.fork.short": "Fork 仓库", "cmd.repo.info.short": "显示仓库详情", "cmd.repo.list.short": "列出用户或组织的仓库", + "cmd.repo.manifest.short": "读取常见依赖清单文件", + "cmd.repo.raw.short": "读取仓库原始文件内容", "cmd.repo.short": "仓库操作", "cmd.repo.tree.short": "列出仓库文件和目录", "cmd.root.long": "用于管理 GitLink 上的仓库、议题、拉取请求、发布、CI 和工作流。", @@ -210,8 +213,12 @@ "flag.repo": "仓库名称(自动从 git remote 检测)", "flag.repo.category": "筛选:manage/mirror/sync/fork/all(默认:manage)", "flag.repo.description": "仓库描述", + "flag.repo.file_exists.path": "要检查的文件路径", + "flag.repo.manifest.kind": "清单类型:go、node、python、rust 或 java", "flag.repo.name": "仓库名称", "flag.repo.private": "设为私有仓库(true/false)", + "flag.repo.raw.path": "仓库中的文件路径", + "flag.repo.raw.ref": "分支、标签或提交引用", "flag.repo.tree.path": "要列出的目录路径(默认:仓库根目录)", "flag.repo.tree.ref": "分支、标签或提交引用", "flag.search.keyword": "搜索关键词", diff --git a/shortcuts/repo/repo.go b/shortcuts/repo/repo.go index 06774a6..54eeca9 100644 --- a/shortcuts/repo/repo.go +++ b/shortcuts/repo/repo.go @@ -108,6 +108,33 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "raw", + Description: tr.T("cmd.repo.raw.short"), + Flags: []common.Flag{ + {Name: "path", Short: "p", Usage: tr.T("flag.repo.raw.path"), Required: true}, + {Name: "ref", Short: "r", Usage: tr.T("flag.repo.raw.ref"), Default: "master"}, + }, + Run: runRawFile, + }, + { + Name: "file-exists", + Description: tr.T("cmd.repo.file_exists.short"), + Flags: []common.Flag{ + {Name: "path", Short: "p", Usage: tr.T("flag.repo.file_exists.path"), Required: true}, + {Name: "ref", Short: "r", Usage: tr.T("flag.repo.raw.ref"), Default: "master"}, + }, + Run: runFileExists, + }, + { + Name: "manifest", + Description: tr.T("cmd.repo.manifest.short"), + Flags: []common.Flag{ + {Name: "kind", Short: "k", Usage: tr.T("flag.repo.manifest.kind"), Required: true}, + {Name: "ref", Short: "r", Usage: tr.T("flag.repo.raw.ref"), Default: "master"}, + }, + Run: runManifest, + }, { Name: "languages", Description: "Show repository language statistics", @@ -256,6 +283,14 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { } } +var manifestPathsByKind = map[string][]string{ + "go": {"go.mod"}, + "node": {"package.json"}, + "python": {"requirements.txt", "pyproject.toml", "Pipfile", "setup.py"}, + "rust": {"Cargo.toml"}, + "java": {"pom.xml", "build.gradle", "build.gradle.kts"}, +} + func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator { if len(translators) > 0 && translators[0] != nil { return translators[0] @@ -263,6 +298,79 @@ func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator { return i18n.Default() } +func runRawFile(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + path, err := requireRepoPathArg(ctx, "path") + if err != nil { + return err + } + ref := repoRef(ctx.Arg("ref")) + env, err := ctx.CallAPI("GET", rawFilePath(ctx, ref, path), nil) + if err != nil { + return err + } + return ctx.Output(env) +} + +func runFileExists(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + path, err := requireRepoPathArg(ctx, "path") + if err != nil { + return err + } + ref := repoRef(ctx.Arg("ref")) + exists, entry, err := repoFileExists(ctx, path, ref) + if err != nil { + return err + } + return ctx.OutputData(map[string]interface{}{ + "repository": fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo), + "ref": ref, + "path": path, + "exists": exists, + "entry": entry, + }) +} + +func runManifest(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + kind := strings.ToLower(strings.TrimSpace(ctx.Arg("kind"))) + paths, ok := manifestPathsByKind[kind] + if !ok { + return fmt.Errorf("invalid --kind %q: use go, node, python, rust, or java", ctx.Arg("kind")) + } + ref := repoRef(ctx.Arg("ref")) + checked := make([]string, 0, len(paths)) + for _, path := range paths { + checked = append(checked, path) + exists, _, err := repoFileExists(ctx, path, ref) + if err != nil { + return err + } + if !exists { + continue + } + env, err := ctx.CallAPI("GET", rawFilePath(ctx, ref, path), nil) + if err != nil { + return err + } + return ctx.Output(env) + } + return ctx.OutputData(map[string]interface{}{ + "repository": fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo), + "ref": ref, + "kind": kind, + "found": false, + "checked": checked, + }) +} + func runLanguages(ctx *common.RuntimeContext) error { if err := ctx.ResolveOwnerRepo(); err != nil { return err @@ -482,6 +590,116 @@ func setRepoQueryIfPresent(q url.Values, key, value string) { } } +func repoRef(ref string) string { + if ref := strings.TrimSpace(ref); ref != "" { + return ref + } + return "master" +} + +func requireRepoPathArg(ctx *common.RuntimeContext, name string) (string, error) { + path, err := ctx.RequireArg(name) + if err != nil { + return "", err + } + path = cleanRepoFilePath(path) + if path == "" { + return "", fmt.Errorf("--%s must not be empty", name) + } + if hasParentPathSegment(path) { + return "", fmt.Errorf("--%s must not contain '..'", name) + } + return path, nil +} + +func cleanRepoFilePath(path string) string { + path = strings.TrimSpace(path) + path = strings.TrimPrefix(path, "/") + return strings.Trim(path, "/") +} + +func rawFilePath(ctx *common.RuntimeContext, ref, path string) string { + parts := []string{ctx.RepoPath(), "raw", url.PathEscape(ref)} + for _, part := range strings.Split(cleanRepoFilePath(path), "/") { + if part == "" { + continue + } + parts = append(parts, url.PathEscape(part)) + } + return strings.Join(parts, "/") +} + +func hasParentPathSegment(path string) bool { + for _, part := range strings.Split(cleanRepoFilePath(path), "/") { + if part == ".." { + return true + } + } + return false +} + +func repoFileExists(ctx *common.RuntimeContext, path, ref string) (bool, map[string]interface{}, error) { + q := url.Values{} + q.Set("filepath", path) + q.Set("ref", ref) + env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/sub_entries", q) + if err != nil { + return false, nil, err + } + entry := findRepoSubEntry(env.Data, path) + return entry != nil, entry, nil +} + +func findRepoSubEntry(data interface{}, path string) map[string]interface{} { + target := lastRepoPathPart(path) + if entry, ok := data.(map[string]interface{}); ok { + if matchesRepoEntry(entry, target, path) { + return entry + } + for _, key := range []string{"entries", "sub_entries", "files", "data"} { + if found := findRepoSubEntryList(entry[key], target, path); found != nil { + return found + } + } + } + return findRepoSubEntryList(data, target, path) +} + +func findRepoSubEntryList(data interface{}, target, path string) map[string]interface{} { + entries, ok := data.([]interface{}) + if !ok { + return nil + } + for _, raw := range entries { + entry, ok := raw.(map[string]interface{}) + if !ok { + continue + } + if matchesRepoEntry(entry, target, path) { + return entry + } + } + return nil +} + +func matchesRepoEntry(entry map[string]interface{}, target, path string) bool { + for _, key := range []string{"name", "filename", "path", "filepath"} { + value, _ := entry[key].(string) + if value == target || cleanRepoFilePath(value) == path { + return true + } + } + return false +} + +func lastRepoPathPart(path string) string { + parts := strings.Split(cleanRepoFilePath(path), "/") + if len(parts) == 0 { + return "" + } + return parts[len(parts)-1] +} + func parseOptionalRepoNonNegativeInt(value, name string) (int, bool, error) { if strings.TrimSpace(value) == "" { return 0, false, nil diff --git a/shortcuts/repo/repo_test.go b/shortcuts/repo/repo_test.go index 5c5f34b..c2f2edb 100644 --- a/shortcuts/repo/repo_test.go +++ b/shortcuts/repo/repo_test.go @@ -218,6 +218,143 @@ func TestRepoTreeShortcutRegistersHelpFlags(t *testing.T) { } } +func TestRepoRawUsesRefAndEscapedPath(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Fatalf("expected GET, got %s", r.Method) + } + assertEqual(t, r.URL.EscapedPath(), "/owner/repo/raw/release%2Fv1/docs/My%20File.md") + writeText(t, w, http.StatusOK, "# title\n") + })) + defer server.Close() + + err := runShortcut(t, server, "raw", map[string]string{ + "ref": "release/v1", + "path": "/docs/My File.md", + }) + if err != nil { + t.Fatalf("raw shortcut failed: %v", err) + } +} + +func TestRepoRawDefaultsRefToMaster(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertRequest(t, r, "GET", "/owner/repo/raw/master/LICENSE") + writeText(t, w, http.StatusOK, "MIT License\n") + })) + defer server.Close() + + if err := runShortcut(t, server, "raw", map[string]string{"path": "LICENSE"}); err != nil { + t.Fatalf("raw shortcut failed: %v", err) + } +} + +func TestRepoFileExistsFindsEntryByName(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + assertEqual(t, r.URL.Query().Get("filepath"), "src/main.go") + assertEqual(t, r.URL.Query().Get("ref"), "main") + writeJSON(t, w, map[string]interface{}{ + "entries": []interface{}{ + map[string]interface{}{"name": "main.go", "type": "file"}, + }, + }) + })) + defer server.Close() + + err := runShortcut(t, server, "file-exists", map[string]string{ + "path": "src/main.go", + "ref": "main", + }) + if err != nil { + t.Fatalf("file-exists shortcut failed: %v", err) + } +} + +func TestRepoFileExistsReportsMissing(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + writeJSON(t, w, map[string]interface{}{"entries": []interface{}{}}) + })) + defer server.Close() + + if err := runShortcut(t, server, "file-exists", map[string]string{"path": "missing.txt"}); err != nil { + t.Fatalf("file-exists missing shortcut failed: %v", err) + } +} + +func TestRepoManifestGoReadsGoMod(t *testing.T) { + requests := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests++ + switch requests { + case 1: + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + assertEqual(t, r.URL.Query().Get("filepath"), "go.mod") + assertEqual(t, r.URL.Query().Get("ref"), "master") + writeJSON(t, w, map[string]interface{}{ + "entries": []interface{}{map[string]interface{}{"name": "go.mod", "type": "file"}}, + }) + case 2: + assertRequest(t, r, "GET", "/owner/repo/raw/master/go.mod") + writeText(t, w, http.StatusOK, "module example.com/repo\n") + default: + t.Fatalf("unexpected extra request: %s %s", r.Method, r.URL.Path) + } + })) + defer server.Close() + + if err := runShortcut(t, server, "manifest", map[string]string{"kind": "go"}); err != nil { + t.Fatalf("manifest go shortcut failed: %v", err) + } + assertEqual(t, requests, 2) +} + +func TestRepoManifestPythonFallsBackToPyproject(t *testing.T) { + requests := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests++ + switch requests { + case 1: + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + assertEqual(t, r.URL.Query().Get("filepath"), "requirements.txt") + writeJSON(t, w, map[string]interface{}{"entries": []interface{}{}}) + case 2: + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + assertEqual(t, r.URL.Query().Get("filepath"), "pyproject.toml") + writeJSON(t, w, map[string]interface{}{ + "entries": []interface{}{map[string]interface{}{"path": "pyproject.toml", "type": "file"}}, + }) + case 3: + assertRequest(t, r, "GET", "/owner/repo/raw/master/pyproject.toml") + writeText(t, w, http.StatusOK, "[project]\n") + default: + t.Fatalf("unexpected extra request: %s %s", r.Method, r.URL.Path) + } + })) + defer server.Close() + + if err := runShortcut(t, server, "manifest", map[string]string{"kind": "python"}); err != nil { + t.Fatalf("manifest python shortcut failed: %v", err) + } + assertEqual(t, requests, 3) +} + +func TestRepoManifestReportsNotFound(t *testing.T) { + requests := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests++ + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + writeJSON(t, w, map[string]interface{}{"entries": []interface{}{}}) + })) + defer server.Close() + + if err := runShortcut(t, server, "manifest", map[string]string{"kind": "java"}); err != nil { + t.Fatalf("manifest missing shortcut failed: %v", err) + } + assertEqual(t, requests, 3) +} + func TestRepoLanguagesUsesLanguagesEndpoint(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assertRequest(t, r, "GET", "/owner/repo/languages.json") @@ -526,6 +663,21 @@ func TestRepoInsightValidation(t *testing.T) { shortcut: "unlike", args: map[string]string{"project-id": "-1"}, }, + { + name: "raw parent path", + shortcut: "raw", + args: map[string]string{"path": "../LICENSE"}, + }, + { + name: "file exists parent path", + shortcut: "file-exists", + args: map[string]string{"path": "src/../LICENSE"}, + }, + { + name: "unknown manifest kind", + shortcut: "manifest", + args: map[string]string{"kind": "ruby"}, + }, } for _, tc := range cases { @@ -573,6 +725,30 @@ func TestRepoInfoHTTPError(t *testing.T) { } } +func TestRepoRawHTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeText(t, w, http.StatusInternalServerError, "server error") + })) + defer server.Close() + + err := runShortcut(t, server, "raw", map[string]string{"path": "README.md"}) + if err == nil { + t.Fatal("expected error for HTTP 500") + } +} + +func TestRepoFileExistsHTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeText(t, w, http.StatusInternalServerError, "server error") + })) + defer server.Close() + + err := runShortcut(t, server, "file-exists", map[string]string{"path": "README.md"}) + if err == nil { + t.Fatal("expected error for HTTP 500") + } +} + func TestRepoForkHTTPError(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeText(t, w, http.StatusInternalServerError, "server error") diff --git a/skills/gitlink-code-review/SKILL.md b/skills/gitlink-code-review/SKILL.md index 3294536..8a9cef5 100644 --- a/skills/gitlink-code-review/SKILL.md +++ b/skills/gitlink-code-review/SKILL.md @@ -164,14 +164,14 @@ gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{ gitlink-cli repo +info --owner --repo --format json # 2. 获取仓库文件列表(遍历关键目录) -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=src&ref=master' -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=tests&ref=master' +gitlink-cli repo +tree --owner --repo --path src --ref master --format json +gitlink-cli repo +tree --owner --repo --path tests --ref master --format json # 3. 获取关键文件内容 -gitlink-cli api GET /:owner/:repo/raw/master/README.md -gitlink-cli api GET /:owner/:repo/raw/master/.gitignore -gitlink-cli api GET /:owner/:repo/raw/master/.eslintrc.js # 或类似配置 -gitlink-cli api GET /:owner/:repo/raw/master/package.json # 或 go.mod, Cargo.toml +gitlink-cli repo +raw --owner --repo --path README.md --ref master --format json +gitlink-cli repo +raw --owner --repo --path .gitignore --ref master --format json +gitlink-cli repo +raw --owner --repo --path .eslintrc.js --ref master --format json +gitlink-cli repo +manifest --owner --repo --kind node --ref master --format json # 4. 获取语言统计和贡献者 gitlink-cli api GET /:owner/:repo/languages @@ -273,7 +273,7 @@ gitlink-cli api GET /:owner/:repo/pulls/:id/diff --format json gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{"body":"...","event":"COMMENT"}' # 获取仓库文件列表 -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=&ref=' +gitlink-cli repo +tree --owner --repo --path --ref --format json # 获取仓库语言统计 gitlink-cli api GET /:owner/:repo/languages --format json diff --git a/skills/gitlink-compliance/SKILL.md b/skills/gitlink-compliance/SKILL.md index bee901a..42c7f03 100644 --- a/skills/gitlink-compliance/SKILL.md +++ b/skills/gitlink-compliance/SKILL.md @@ -39,10 +39,10 @@ metadata: ```bash # 1. 获取仓库文件结构 -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=&ref=master' +gitlink-cli repo +tree --owner --repo --ref master --format json # 2. 读取 LICENSE 文件 -gitlink-cli api GET /:owner/:repo/raw/master/LICENSE +gitlink-cli repo +raw --owner --repo --path LICENSE --ref master --format json # 3. 检查关键文档是否存在 # 检查以下文件是否存在: @@ -54,14 +54,14 @@ gitlink-cli api GET /:owner/:repo/raw/master/LICENSE # - README.md # 4. 获取依赖配置 -gitlink-cli api GET /:owner/:repo/raw/master/package.json # Node.js -gitlink-cli api GET /:owner/:repo/raw/master/go.mod # Go -gitlink-cli api GET /:owner/:repo/raw/master/requirements.txt # Python -gitlink-cli api GET /:owner/:repo/raw/master/Cargo.toml # Rust -gitlink-cli api GET /:owner/:repo/raw/master/pom.xml # Java/Maven +gitlink-cli repo +manifest --owner --repo --kind node --ref master --format json +gitlink-cli repo +manifest --owner --repo --kind go --ref master --format json +gitlink-cli repo +manifest --owner --repo --kind python --ref master --format json +gitlink-cli repo +manifest --owner --repo --kind rust --ref master --format json +gitlink-cli repo +manifest --owner --repo --kind java --ref master --format json # 5. 获取源文件检查(按语言采样) -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=src&ref=master' +gitlink-cli repo +tree --owner --repo --path src --ref master --format json # 6. 获取仓库基本信息 gitlink-cli repo +info --owner --repo --format json @@ -146,7 +146,7 @@ gitlink-cli repo +info --owner --repo --format json ```bash # 1. 获取依赖配置文件 -gitlink-cli api GET /:owner/:repo/raw/master/package.json +gitlink-cli repo +manifest --owner --repo --kind node --ref master --format json ``` ### 许可证兼容性参考 @@ -181,10 +181,10 @@ gitlink-cli api GET /:owner/:repo/raw/master/package.json ```bash # 1. 遍历源文件目录 -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=src&ref=master' +gitlink-cli repo +tree --owner --repo --path src --ref master --format json # 2. 采样检查源文件头部(取前 5-10 行) -gitlink-cli api GET /:owner/:repo/raw/master/src/main.py +gitlink-cli repo +raw --owner --repo --path src/main.py --ref master --format json ``` ### 标准版权声明模板 @@ -210,10 +210,10 @@ gitlink-cli api GET /:owner/:repo/raw/master/src/main.py ```bash # 获取文件内容 -gitlink-cli api GET /:owner/:repo/raw// +gitlink-cli repo +raw --owner --repo --path --ref --format json # 获取文件列表(遍历目录) -gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=&ref=' +gitlink-cli repo +tree --owner --repo --path --ref --format json # 获取仓库信息 gitlink-cli api GET /:owner/:repo --format json diff --git a/skills/gitlink-repo/SKILL.md b/skills/gitlink-repo/SKILL.md index 5d59705..15be85b 100644 --- a/skills/gitlink-repo/SKILL.md +++ b/skills/gitlink-repo/SKILL.md @@ -1,7 +1,7 @@ --- name: gitlink-repo -version: 1.0.0 -description: "仓库管理:创建、查看、Fork、删除仓库,查看 README、语言统计、贡献者、关注者,并执行关注/点赞等互动操作。当用户需要操作或分析 GitLink 仓库时触发。" +version: 2.0.0 +description: "仓库管理:创建、查看、Fork、删除仓库,查看 README、文件树、原始文件、依赖清单、语言统计、贡献者、关注者,并执行关注/点赞等互动操作。当用户需要操作或分析 GitLink 仓库时触发。" metadata: requires: bins: ["gitlink-cli"] @@ -24,6 +24,9 @@ metadata: | `repo +info` | 仓库详情 | 否(公开项目) | | `repo +readme` | README 内容 | 否(公开项目) | | `repo +tree` | 仓库文件树 | 否(公开项目) | +| `repo +raw` | 读取原始文件内容 | 否(公开项目) | +| `repo +file-exists` | 检查文件是否存在 | 否(公开项目) | +| `repo +manifest` | 读取常见依赖清单 | 否(公开项目) | | `repo +languages` | 仓库语言统计 | 否(公开项目) | | `repo +contributors` | 仓库贡献者列表 | 否(公开项目) | | `repo +contributor-stats` | 贡献者代码行统计 | 否(公开项目) | @@ -54,6 +57,9 @@ gitlink-cli repo +list --user zhangsan # 查看文件树、语言占比和贡献者 gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main +gitlink-cli repo +raw --owner Gitlink --repo forgeplus --path LICENSE --ref master +gitlink-cli repo +file-exists --owner Gitlink --repo forgeplus --path package.json --ref master +gitlink-cli repo +manifest --owner Gitlink --repo forgeplus --kind go --ref master gitlink-cli repo +languages --owner Gitlink --repo forgeplus gitlink-cli repo +contributors --owner Gitlink --repo forgeplus @@ -93,11 +99,11 @@ gitlink-cli api GET /:owner/:repo/commits --query 'page=1&limit=20' # 获取标签列表 gitlink-cli api GET /:owner/:repo/tags -# 获取文件内容 -gitlink-cli api GET /:owner/:repo/raw/main/README.md ``` ## 注意事项 - `repo +delete` 是不可逆操作,执行前必须确认用户意图 - 创建仓库默认为公开,使用 `--private true` 创建私有仓库 +- `repo +raw` 会读取指定 `--ref` 下的文件内容,默认 `master` +- `repo +manifest --kind` 支持 `go`、`node`、`python`、`rust`、`java`