feat(repo): add raw file content shortcuts

This commit is contained in:
NeeNe 2026-07-02 14:29:30 +08:00
parent 71ca2bb683
commit 1db1b39d05
10 changed files with 523 additions and 24 deletions

View File

@ -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

View File

@ -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

View File

@ -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/<ref>/<path>` 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 ./...
```

View File

@ -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",

View File

@ -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": "搜索关键词",

View File

@ -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

View File

@ -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")

View File

@ -164,14 +164,14 @@ gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{
gitlink-cli repo +info --owner <owner> --repo <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 <owner> --repo <repo> --path src --ref master --format json
gitlink-cli repo +tree --owner <owner> --repo <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 <owner> --repo <repo> --path README.md --ref master --format json
gitlink-cli repo +raw --owner <owner> --repo <repo> --path .gitignore --ref master --format json
gitlink-cli repo +raw --owner <owner> --repo <repo> --path .eslintrc.js --ref master --format json
gitlink-cli repo +manifest --owner <owner> --repo <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=<path>&ref=<branch>'
gitlink-cli repo +tree --owner <owner> --repo <repo> --path <path> --ref <branch> --format json
# 获取仓库语言统计
gitlink-cli api GET /:owner/:repo/languages --format json

View File

@ -39,10 +39,10 @@ metadata:
```bash
# 1. 获取仓库文件结构
gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=&ref=master'
gitlink-cli repo +tree --owner <owner> --repo <repo> --ref master --format json
# 2. 读取 LICENSE 文件
gitlink-cli api GET /:owner/:repo/raw/master/LICENSE
gitlink-cli repo +raw --owner <owner> --repo <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 <owner> --repo <repo> --kind node --ref master --format json
gitlink-cli repo +manifest --owner <owner> --repo <repo> --kind go --ref master --format json
gitlink-cli repo +manifest --owner <owner> --repo <repo> --kind python --ref master --format json
gitlink-cli repo +manifest --owner <owner> --repo <repo> --kind rust --ref master --format json
gitlink-cli repo +manifest --owner <owner> --repo <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 <owner> --repo <repo> --path src --ref master --format json
# 6. 获取仓库基本信息
gitlink-cli repo +info --owner <owner> --repo <repo> --format json
@ -146,7 +146,7 @@ gitlink-cli repo +info --owner <owner> --repo <repo> --format json
```bash
# 1. 获取依赖配置文件
gitlink-cli api GET /:owner/:repo/raw/master/package.json
gitlink-cli repo +manifest --owner <owner> --repo <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 <owner> --repo <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 <owner> --repo <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/<branch>/<path>
gitlink-cli repo +raw --owner <owner> --repo <repo> --path <path> --ref <branch> --format json
# 获取文件列表(遍历目录)
gitlink-cli api GET /:owner/:repo/sub_entries --query 'filepath=<path>&ref=<branch>'
gitlink-cli repo +tree --owner <owner> --repo <repo> --path <path> --ref <branch> --format json
# 获取仓库信息
gitlink-cli api GET /:owner/:repo --format json

View File

@ -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`