Merge PR #82: feat(meta): add attachment and metadata shortcuts
# Conflicts: # README.md # README.zh-CN.md # shortcuts/attachment/attachment.go # shortcuts/attachment/attachment_test.go # shortcuts/register.go # shortcuts/register_test.go
This commit is contained in:
commit
905b078e00
184
README.md
184
README.md
|
|
@ -5,7 +5,7 @@
|
|||
[](https://golang.org)
|
||||
[](https://www.npmjs.com/package/@gitlink-ai/cli)
|
||||
|
||||
The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/README.md).
|
||||
The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, issue tracking, pull requests, webhooks, attachments, metadata lookup, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/).
|
||||
|
||||
**[中文文档](./README.zh-CN.md)**
|
||||
|
||||
|
|
@ -78,20 +78,12 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
|||
<a href="https://www.gitlink.org.cn/jiangtx" title="jiangtx"><img src="https://www.gitlink.org.cn/system/lets/letter_avatars/2/J/67_157_94/120.png" width="40" height="40" alt="jiangtx" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/jiangtx">jiangtx</a></sub>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://www.gitlink.org.cn/luwanzhou" title="luwanzhou"><img src="https://www.gitlink.org.cn/system/lets/letter_avatars/2/L/165_135_246/120.png" width="40" height="40" alt="luwanzhou" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/luwanzhou">luwanzhou</a></sub>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://www.gitlink.org.cn/whale_hihihi" title="whale_hihihi"><img src="https://www.gitlink.org.cn/images/avatars/User/137722?t=1778575729" width="40" height="40" alt="whale_hihihi" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/whale_hihihi">whale_hihihi</a></sub>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Why gitlink-cli?
|
||||
|
||||
- **Agent-Native Design** — Structured [Skills](./skills/README.md) out of the box, compatible with Claude Code, OpenClaw, and other AI platforms — Agents can operate GitLink with zero extra setup
|
||||
- **Wide Coverage** — Repository, Issue, PR, Webhook, Member, Branch, Release, CI, Pipeline, Org, Search, and User workflows are covered by high-level commands
|
||||
- **Agent-Native Design** — Structured [Skills](./skills/) out of the box, compatible with Claude Code, OpenClaw, and other AI platforms — Agents can operate GitLink with zero extra setup
|
||||
- **Wide Coverage** — Repository, Issue, PR, Webhook, Attachment, Metadata, Member, Branch, Release, CI, Pipeline, Org, Search, and User workflows are covered by high-level commands
|
||||
- **AI-Friendly & Optimized** — Every command is tested with real Agents, featuring concise parameters, smart defaults, and structured output
|
||||
- **Cross-Platform** — Runs on macOS, Linux, and Windows (x64/arm64), install via `npm install -g @gitlink-ai/cli` in one command, binary auto-downloaded
|
||||
- **Open Source, Zero Barriers** — MulanPSL-2.0 license, ready to use, just `npm install`
|
||||
|
|
@ -104,21 +96,20 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
|||
| Category | Capabilities |
|
||||
|----------|-------------|
|
||||
| 📦 Repo | List, create, fork, delete repositories, view repo info, insights, and interactions |
|
||||
| 🐛 Issue | Create, update, close, batch close/update/delete, comment on issues |
|
||||
| 🐛 Issue | Create, update, close, batch close, comment on issues |
|
||||
| 🔖 Label | Create, list, update, delete issue labels |
|
||||
| 🔀 PR | Create, merge, review pull requests, view changed files |
|
||||
| 👥 Member | List, add, remove repository members, change roles, create and accept invite links |
|
||||
| 🌿 Branch | Create, delete, restore, list, filter, protect, unprotect, and switch default branches |
|
||||
| 🌿 Branch | Create, delete, list, protect, unprotect branches |
|
||||
| 🏷️ Release | Create, edit, update, view, delete releases |
|
||||
| 🏢 Org | Manage organizations, members, 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 |
|
||||
| 📖 Wiki | List, view, create, update, and delete wiki pages |
|
||||
| 📎 Attachment | Upload files and delete attachments with dry-run safety |
|
||||
| 🧭 Metadata | List license and .gitignore templates |
|
||||
| 🔍 Search | Search repositories, users |
|
||||
| 📊 Dataset | Query research datasets by project |
|
||||
| 👤 User | View user profiles and info |
|
||||
| 📊 Profile | User ability, role, major, activity, and contribution statistics |
|
||||
| 📋 PM | Sprint management, kanban boards, weekly reports |
|
||||
| 🤖 Workflow | AI-powered issue triage, PR review, release notes |
|
||||
|
||||
|
|
@ -274,28 +265,6 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
|
|||
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
|
||||
```
|
||||
|
||||
### Wiki Management
|
||||
|
||||
```bash
|
||||
# List wiki pages (table of contents)
|
||||
gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
|
||||
|
||||
# View a wiki page by page name
|
||||
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
|
||||
|
||||
# Create a wiki page
|
||||
gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||
-n getting-started -t "Getting Started" -c "# Getting Started Guide"
|
||||
|
||||
# Update a wiki page title and/or content
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title"
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# Updated content"
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title" -c "New content"
|
||||
|
||||
# Delete a wiki page
|
||||
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
|
||||
```
|
||||
|
||||
### Member Management
|
||||
|
||||
```bash
|
||||
|
|
@ -345,14 +314,6 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
|
|||
# Batch close issues from a CSV file
|
||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
|
||||
|
||||
# Preview batch metadata update by API issue IDs
|
||||
# Note: --ids uses API issue IDs, not web URL issue numbers.
|
||||
gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --ids 101,102 --status-id 3 --priority-id 2 --dry-run
|
||||
|
||||
# Destructive batch delete requires both dry-run first and --yes for real execution
|
||||
gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --dry-run
|
||||
gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --yes
|
||||
|
||||
# Add a comment
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Fixed"
|
||||
|
||||
|
|
@ -396,27 +357,6 @@ gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
|
|||
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
|
||||
```
|
||||
|
||||
### Template Management
|
||||
|
||||
```bash
|
||||
# List project templates
|
||||
gitlink-cli template +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# Get a template by id
|
||||
gitlink-cli template +get --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# Create an issue template
|
||||
gitlink-cli template +create --owner Gitlink --repo forgeplus \
|
||||
-t "ProjectTemplates::Issue" -n "Bug Report" -c "## Description\n## Steps to Reproduce\n## Expected Behavior"
|
||||
|
||||
# Update a template
|
||||
gitlink-cli template +update --owner Gitlink --repo forgeplus \
|
||||
-i 5 -t "ProjectTemplates::Issue" -n "Bug Report v2" -c "## Updated content"
|
||||
|
||||
# Delete a template
|
||||
gitlink-cli template +delete --owner Gitlink --repo forgeplus -i 5
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
|
||||
```bash
|
||||
|
|
@ -432,9 +372,6 @@ gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: New feature" -
|
|||
# View a PR
|
||||
gitlink-cli pr +view --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# Fetch and checkout a PR branch locally
|
||||
gitlink-cli pr +checkout --owner Gitlink --repo forgeplus -i 42 --branch review/pr-42
|
||||
|
||||
# Merge a PR
|
||||
gitlink-cli pr +merge --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
|
|
@ -464,24 +401,12 @@ gitlink-cli pr +review --owner Gitlink --repo forgeplus -i 42 --status approved
|
|||
# List branches
|
||||
gitlink-cli branch +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# List deleted branches or search by keyword
|
||||
gitlink-cli branch +list --owner Gitlink --repo forgeplus --state deleted --keyword release/
|
||||
|
||||
# List all branches with archive download URLs
|
||||
gitlink-cli branch +all --owner Gitlink --repo forgeplus
|
||||
|
||||
# Create a branch
|
||||
gitlink-cli branch +create --name feature/new-feature
|
||||
|
||||
# Delete a branch
|
||||
gitlink-cli branch +delete --name feature/old-feature
|
||||
|
||||
# Set the default branch
|
||||
gitlink-cli branch +set-default --name main
|
||||
|
||||
# Restore a deleted branch
|
||||
gitlink-cli branch +restore --branch-id 7 --name feature/old-feature
|
||||
|
||||
# Protect a branch
|
||||
gitlink-cli branch +protect --name main
|
||||
|
||||
|
|
@ -544,36 +469,6 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow
|
|||
gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run
|
||||
```
|
||||
|
||||
### Project Management
|
||||
|
||||
`pm` surfaces GitLink project-management data for dashboards, sprints, weekly
|
||||
issues, labels, pipelines, and action runs. PM commands require the numeric
|
||||
GitLink project ID.
|
||||
|
||||
```bash
|
||||
# Query PM dashboards and sprint issues
|
||||
gitlink-cli pm +dashboards --project-id 123 --limit 20
|
||||
gitlink-cli pm +sprint-issues --project-id 123 --page 1 --limit 20
|
||||
|
||||
# Query weekly issues and PM issue tags
|
||||
gitlink-cli pm +weekly-issues --project-id 123
|
||||
gitlink-cli pm +issue-tags --project-id 123
|
||||
|
||||
# Query PM pipelines and action runs
|
||||
gitlink-cli pm +pipelines --project-id 123
|
||||
gitlink-cli pm +action-runs --project-id 123
|
||||
```
|
||||
|
||||
### Ignore File Templates
|
||||
|
||||
```bash
|
||||
# List all available .gitignore templates
|
||||
gitlink-cli ignore +list
|
||||
|
||||
# Filter templates by name
|
||||
gitlink-cli ignore +list --name Go
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
```bash
|
||||
|
|
@ -584,27 +479,23 @@ gitlink-cli search +repos -k "machine learning"
|
|||
gitlink-cli search +users -k "zhangsan"
|
||||
```
|
||||
|
||||
### User Profile
|
||||
|
||||
`profile` surfaces GitLink's native user statistics (ability, role, major, activity,
|
||||
contribution). When `--user` is omitted it defaults to the authenticated user.
|
||||
### Attachment and Metadata
|
||||
|
||||
```bash
|
||||
# Development ability scores + language breakdown
|
||||
gitlink-cli profile +ability --user zhangsan
|
||||
# List license templates
|
||||
gitlink-cli meta +licenses --name MIT
|
||||
|
||||
# Role positioning / major (discipline) categories
|
||||
gitlink-cli profile +role --user zhangsan
|
||||
gitlink-cli profile +major --user zhangsan
|
||||
# List .gitignore templates
|
||||
gitlink-cli meta +ignores --name Go
|
||||
|
||||
# Ability within a time window (Unix timestamps)
|
||||
gitlink-cli profile +ability --user zhangsan --start-time 1704067200 --end-time 1735689600
|
||||
# Preview an attachment upload without changing data
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "issue screenshot" --dry-run
|
||||
|
||||
# Recent activity (issues / PRs / commits per day) for the current user
|
||||
gitlink-cli profile +activity
|
||||
# Upload an attachment
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "issue screenshot"
|
||||
|
||||
# Contribution heatmap for a given year
|
||||
gitlink-cli profile +contribution --user zhangsan --year 2025
|
||||
# Preview attachment deletion
|
||||
gitlink-cli attachment +delete --uuid <uuid> --dry-run
|
||||
```
|
||||
|
||||
### Workflow Agent Commands
|
||||
|
|
@ -706,32 +597,6 @@ Safety:
|
|||
- `workflow +pr-summary` does not comment, approve, reject, or merge pull requests.
|
||||
- `workflow +repo-report` aggregates health, issue triage, and PR review summary signals without remote writes.
|
||||
|
||||
### Dataset
|
||||
|
||||
`dataset` manages and queries GitLink research datasets (title, description,
|
||||
paper content, license, owning project).
|
||||
|
||||
```bash
|
||||
# List datasets for one or more projects (by numeric project ID)
|
||||
gitlink-cli dataset +list --ids 5988
|
||||
|
||||
# View a repository's dataset and attachments
|
||||
gitlink-cli dataset +view --owner Gitlink --repo forgeplus
|
||||
|
||||
# Create / update a repository's dataset (preview first with --dry-run)
|
||||
gitlink-cli dataset +create --owner me --repo proj -t "My dataset" -d "..." --license-id 359 --dry-run
|
||||
gitlink-cli dataset +update --owner me --repo proj -t "My dataset" -d "updated"
|
||||
|
||||
# Delete a dataset attachment (destructive: preview, then confirm with --yes)
|
||||
gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --dry-run
|
||||
gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --yes
|
||||
```
|
||||
|
||||
> Note: `dataset +list` (platform dataset query) is verified on production
|
||||
> gitlink.org.cn. The per-repo `+view`/`+create`/`+update` routes follow the
|
||||
> published OpenAPI contract but are not yet deployed on production (they return
|
||||
> 404 there); they will work once the platform enables them.
|
||||
|
||||
### Raw API
|
||||
|
||||
For endpoints not covered by shortcuts, use the Raw API directly:
|
||||
|
|
@ -788,19 +653,21 @@ git push gitlink
|
|||
|
||||
The `skills/` directory contains Agent Skill files for AI-automated GitLink operations.
|
||||
|
||||
See [skills/README.md](./skills/README.md) for details.
|
||||
See [skills/README.md](skills/README.md) for details.
|
||||
|
||||
| Skill | Description |
|
||||
|-------|-------------|
|
||||
| `gitlink-shared` | Authentication, global parameters, safety rules, API notes |
|
||||
| `gitlink-repo` | Repository operations (create, view, delete, fork, insights, etc.) |
|
||||
| `gitlink-issue` | Issue operations (create, update, close, batch update/delete, comment, etc.) |
|
||||
| `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 |
|
||||
| `gitlink-branch` | Branch management (create, delete, list, protect, unprotect) |
|
||||
| `gitlink-release` | Release management (create, edit, update, view, delete, etc.) |
|
||||
| `gitlink-ci` | CI/CD operations (builds, logs, etc.) |
|
||||
| `gitlink-pipeline` | Pipeline workflow operations (runs, logs, enable, disable, delete, etc.) |
|
||||
| `gitlink-attachment` | Attachment upload and deletion workflows |
|
||||
| `gitlink-meta` | Read-only license and .gitignore template lookup |
|
||||
| `gitlink-search` | Search (repositories, users, etc.) |
|
||||
| `gitlink-org` | Organization management (members, teams, etc.) |
|
||||
| `gitlink-user` | User management (profile info, etc.) |
|
||||
|
|
@ -835,9 +702,10 @@ gitlink-cli/
|
|||
│ ├── org/ # Organization shortcuts
|
||||
│ ├── ci/ # CI shortcuts
|
||||
│ ├── pipeline/ # Pipeline shortcuts
|
||||
│ ├── pm/ # Project management shortcuts
|
||||
│ ├── search/ # Search shortcuts
|
||||
│ ├── user/ # User shortcuts
|
||||
│ ├── attachment/ # Attachment shortcuts
|
||||
│ ├── meta/ # Metadata shortcuts
|
||||
│ └── register.go # Registration entry point
|
||||
├── skills/ # AI Agent Skills
|
||||
│ ├── README.md # Skills guide
|
||||
|
|
@ -859,7 +727,7 @@ gitlink-cli/
|
|||
|
||||
## Documentation
|
||||
|
||||
- [Skills Guide](./skills/README.md) — AI Agent Skills detailed documentation
|
||||
- [Skills Guide](skills/README.md) — AI Agent Skills detailed documentation
|
||||
- [Design Document](doc/design.md) — Architecture design and development plan
|
||||
|
||||
## FAQ
|
||||
|
|
@ -924,7 +792,7 @@ gitlink-cli uses Windows Credential Manager for secure token storage. If Credent
|
|||
|
||||
### Q: Where can I find the full API reference?
|
||||
|
||||
See [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md).
|
||||
See [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md).
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
144
README.zh-CN.md
144
README.zh-CN.md
|
|
@ -5,7 +5,7 @@
|
|||
[](https://golang.org)
|
||||
[](https://www.npmjs.com/package/@gitlink-ai/cli)
|
||||
|
||||
[GitLink(确实开源)](https://www.gitlink.org.cn) 官方 CLI 工具 — 为人类和 AI Agent 双重设计。支持 **macOS、Linux、Windows**,覆盖仓库管理、Issue 追踪、Pull Request、Webhook、成员协作、CI/CD 和 AI 自动化工作流,包含 40+ 命令和 AI Agent [Skills](./skills/README.md)。
|
||||
[GitLink(确实开源)](https://www.gitlink.org.cn) 官方 CLI 工具 — 为人类和 AI Agent 双重设计。支持 **macOS、Linux、Windows**,覆盖仓库管理、Issue 追踪、Pull Request、Webhook、附件、元数据查询、成员协作、CI/CD 和 AI 自动化工作流,包含 40+ 命令和 AI Agent [Skills](./skills/)。
|
||||
|
||||
**[English](./README.md)**
|
||||
|
||||
|
|
@ -78,20 +78,12 @@
|
|||
<a href="https://www.gitlink.org.cn/jiangtx" title="jiangtx"><img src="https://www.gitlink.org.cn/system/lets/letter_avatars/2/J/67_157_94/120.png" width="40" height="40" alt="jiangtx" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/jiangtx">jiangtx</a></sub>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://www.gitlink.org.cn/luwanzhou" title="luwanzhou"><img src="https://www.gitlink.org.cn/system/lets/letter_avatars/2/L/165_135_246/120.png" width="40" height="40" alt="luwanzhou" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/luwanzhou">luwanzhou</a></sub>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://www.gitlink.org.cn/whale_hihihi" title="whale_hihihi"><img src="https://www.gitlink.org.cn/images/avatars/User/137722?t=1778575729" width="40" height="40" alt="whale_hihihi" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/whale_hihihi">whale_hihihi</a></sub>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## 为什么选择 gitlink-cli?
|
||||
|
||||
- **Agent-Native 设计** — 开箱即用结构化 [Skills](./skills/README.md),兼容 Claude Code — Agent 零配置即可操作 GitLink
|
||||
- **广泛覆盖** — 仓库、Issue、PR、Webhook、成员、分支、Release、CI、Pipeline、组织、搜索、用户等常用工作流均提供高层命令
|
||||
- **Agent-Native 设计** — 开箱即用结构化 [Skills](./skills/),兼容 Claude Code — Agent 零配置即可操作 GitLink
|
||||
- **广泛覆盖** — 仓库、Issue、PR、Webhook、附件、元数据、成员、分支、Release、CI、Pipeline、组织、搜索、用户等常用工作流均提供高层命令
|
||||
- **AI 友好 & 优化** — 每条命令都经过真实 Agent 测试,简洁参数、智能默认值、结构化输出
|
||||
- **跨平台** — macOS、Linux、Windows (x64/arm64) 全支持,`npm` 一条命令安装
|
||||
- **开源零门槛** — 木兰宽松许可证第2版(MulanPSL-2.0),`npm install` 即用
|
||||
|
|
@ -104,7 +96,7 @@
|
|||
| 分类 | 能力 |
|
||||
|------|------|
|
||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
|
||||
| 🐛 Issue | 创建、更新、关闭、批量关闭/更新/删除、评论 Issue |
|
||||
| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
|
||||
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
|
||||
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
|
||||
| 👥 成员 | 列出、添加、移除仓库成员,调整角色,生成和接受邀请链接 |
|
||||
|
|
@ -113,11 +105,10 @@
|
|||
| 🏢 组织 | 管理组织、成员、团队 |
|
||||
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
|
||||
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
|
||||
| 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
|
||||
| 📎 附件 | 上传文件、删除附件,支持 dry-run 预览 |
|
||||
| 🧭 元数据 | 查询许可证模板和 .gitignore 模板 |
|
||||
| 🔍 搜索 | 搜索仓库、用户 |
|
||||
| 📊 数据集 | 按项目查询科研数据集 |
|
||||
| 👤 用户 | 查看用户资料和信息 |
|
||||
| 📊 画像 | 用户开发能力、角色定位、专业定位、近期活动、贡献热力图统计 |
|
||||
| 📋 项目管理 | Sprint 管理、看板、周报 |
|
||||
| 🤖 工作流 | AI 驱动的 Issue 分类、PR Review、Release Notes |
|
||||
|
||||
|
|
@ -285,28 +276,6 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
|
|||
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
|
||||
```
|
||||
|
||||
### Wiki 管理
|
||||
|
||||
```bash
|
||||
# 列出 Wiki 页面(目录结构)
|
||||
gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
|
||||
|
||||
# 查看 Wiki 页面
|
||||
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
|
||||
|
||||
# 创建 Wiki 页面
|
||||
gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||
-n getting-started -t "快速开始" -c "# 快速开始指南"
|
||||
|
||||
# 更新 Wiki 页面标题和/或内容
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题"
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# 更新后的内容"
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题" -c "新内容"
|
||||
|
||||
# 删除 Wiki 页面
|
||||
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
|
||||
```
|
||||
|
||||
### 成员管理
|
||||
|
||||
```bash
|
||||
|
|
@ -356,14 +325,6 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
|
|||
# 从 CSV 文件批量关闭 Issue
|
||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
|
||||
|
||||
# 按 API issue id 预览批量更新元数据
|
||||
# 注意:--ids 是 API issue id,不是网页 URL 中的 Issue 编号。
|
||||
gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --ids 101,102 --status-id 3 --priority-id 2 --dry-run
|
||||
|
||||
# 危险批量删除必须先 dry-run,真实执行还要显式 --yes
|
||||
gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --dry-run
|
||||
gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --yes
|
||||
|
||||
# 添加评论
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "已修复"
|
||||
|
||||
|
|
@ -406,27 +367,6 @@ gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
|
|||
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
|
||||
```
|
||||
|
||||
### 模板管理
|
||||
|
||||
```bash
|
||||
# 列出项目模板
|
||||
gitlink-cli template +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# 按 ID 获取模板详情
|
||||
gitlink-cli template +get --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# 创建 Issue 模板
|
||||
gitlink-cli template +create --owner Gitlink --repo forgeplus \
|
||||
-t "ProjectTemplates::Issue" -n "缺陷报告" -c "## 问题描述\n## 复现步骤\n## 期望行为"
|
||||
|
||||
# 更新模板
|
||||
gitlink-cli template +update --owner Gitlink --repo forgeplus \
|
||||
-i 5 -t "ProjectTemplates::Issue" -n "缺陷报告 v2" -c "## 更新后的内容"
|
||||
|
||||
# 删除模板
|
||||
gitlink-cli template +delete --owner Gitlink --repo forgeplus -i 5
|
||||
```
|
||||
|
||||
### Pull Request
|
||||
|
||||
```bash
|
||||
|
|
@ -442,9 +382,6 @@ gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: 新功能" --h
|
|||
# 查看 PR
|
||||
gitlink-cli pr +view --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# 拉取并切换到 PR 分支
|
||||
gitlink-cli pr +checkout --owner Gitlink --repo forgeplus -i 42 --branch review/pr-42
|
||||
|
||||
# 合并 PR
|
||||
gitlink-cli pr +merge --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
|
|
@ -510,16 +447,6 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow
|
|||
gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run
|
||||
```
|
||||
|
||||
### 忽略文件模板
|
||||
|
||||
```bash
|
||||
# 列出所有可用的 .gitignore 模板
|
||||
gitlink-cli ignore +list
|
||||
|
||||
# 按名称筛选模板
|
||||
gitlink-cli ignore +list --name Go
|
||||
```
|
||||
|
||||
### 搜索
|
||||
|
||||
```bash
|
||||
|
|
@ -530,50 +457,25 @@ gitlink-cli search +repos -k "machine learning"
|
|||
gitlink-cli search +users -k "zhangsan"
|
||||
```
|
||||
|
||||
### 用户画像
|
||||
|
||||
`profile` 暴露 GitLink 原生的用户画像统计(开发能力、角色定位、专业定位、近期活动、贡献热力图)。
|
||||
省略 `--user` 时默认使用当前认证用户。
|
||||
### 附件和元数据
|
||||
|
||||
```bash
|
||||
# 开发能力评分 + 语言分布
|
||||
gitlink-cli profile +ability --user zhangsan
|
||||
# 查询许可证模板
|
||||
gitlink-cli meta +licenses --name MIT
|
||||
|
||||
# 角色定位 / 专业(学科)定位
|
||||
gitlink-cli profile +role --user zhangsan
|
||||
gitlink-cli profile +major --user zhangsan
|
||||
# 查询 .gitignore 模板
|
||||
gitlink-cli meta +ignores --name Go
|
||||
|
||||
# 指定时间范围的开发能力(Unix 时间戳)
|
||||
gitlink-cli profile +ability --user zhangsan --start-time 1704067200 --end-time 1735689600
|
||||
# 预览附件上传,不修改线上数据
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "issue screenshot" --dry-run
|
||||
|
||||
# 当前用户的近期活动(每日 疑修 / 合并请求 / 提交)
|
||||
gitlink-cli profile +activity
|
||||
# 上传附件
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "issue screenshot"
|
||||
|
||||
# 指定年份的贡献热力图
|
||||
gitlink-cli profile +contribution --user zhangsan --year 2025
|
||||
# 预览删除附件
|
||||
gitlink-cli attachment +delete --uuid <uuid> --dry-run
|
||||
```
|
||||
|
||||
### 数据集
|
||||
|
||||
`dataset` 管理并查询 GitLink 科研数据集(标题、描述、论文内容、许可证、所属项目)。
|
||||
|
||||
```bash
|
||||
# 按数字项目 ID 列出一个或多个项目的数据集
|
||||
gitlink-cli dataset +list --ids 5988
|
||||
|
||||
# 查看仓库的数据集及其附件
|
||||
gitlink-cli dataset +view --owner Gitlink --repo forgeplus
|
||||
|
||||
# 创建 / 更新仓库数据集(先用 --dry-run 预览)
|
||||
gitlink-cli dataset +create --owner me --repo proj -t "我的数据集" -d "..." --license-id 359 --dry-run
|
||||
gitlink-cli dataset +update --owner me --repo proj -t "我的数据集" -d "更新"
|
||||
|
||||
# 删除数据集附件(破坏性:先预览,再用 --yes 确认)
|
||||
gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --dry-run
|
||||
gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --yes
|
||||
```
|
||||
|
||||
> 注意:`dataset +list`(平台数据集查询)已在生产 gitlink.org.cn 验证可用。按仓库的 `+view`/`+create`/`+update` 遵循已发布的 OpenAPI 契约,但生产环境尚未部署(当前返回 404),待平台上线后即可生效。
|
||||
### Raw API
|
||||
|
||||
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:
|
||||
|
|
@ -630,19 +532,21 @@ git push gitlink
|
|||
|
||||
`skills/` 目录包含 Claude Code Agent Skill 文件,支持 AI 自动化操作 GitLink 平台。
|
||||
|
||||
详见 [skills/README.md](./skills/README.md)
|
||||
详见 [skills/README.md](skills/README.md)
|
||||
|
||||
| Skill | 说明 |
|
||||
|-------|------|
|
||||
| `gitlink-shared` | 认证、全局参数、安全规则、API 注意事项 |
|
||||
| `gitlink-repo` | 仓库操作(创建、查看、删除、Fork、洞察数据等) |
|
||||
| `gitlink-issue` | Issue 操作(创建、更新、关闭、批量更新/删除、评论等) |
|
||||
| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
|
||||
| `gitlink-pr` | Pull Request 操作(创建、合并、Review 等) |
|
||||
| `gitlink-member` | 仓库成员与邀请链接管理 |
|
||||
| `gitlink-release` | 发布管理(创建、编辑、更新、查看、删除等) |
|
||||
| `gitlink-org` | 组织管理(成员、团队等) |
|
||||
| `gitlink-ci` | CI/CD 操作(构建、日志等) |
|
||||
| `gitlink-pipeline` | 流水线工作流操作(运行、日志、启停、删除等) |
|
||||
| `gitlink-attachment` | 附件上传与删除操作 |
|
||||
| `gitlink-meta` | 许可证和 .gitignore 模板查询 |
|
||||
| `gitlink-search` | 搜索功能(仓库、用户等) |
|
||||
| `gitlink-user` | 用户管理(个人信息等) |
|
||||
| `gitlink-pm` | 项目管理(Sprint、看板、周报等) |
|
||||
|
|
@ -677,6 +581,8 @@ gitlink-cli/
|
|||
│ ├── pipeline/ # Pipeline shortcuts
|
||||
│ ├── search/ # 搜索 shortcuts
|
||||
│ ├── user/ # 用户 shortcuts
|
||||
│ ├── attachment/ # 附件 shortcuts
|
||||
│ ├── meta/ # 元数据 shortcuts
|
||||
│ └── register.go # 注册入口
|
||||
├── skills/ # AI Agent Skills
|
||||
│ ├── README.md # Skills 使用指南
|
||||
|
|
@ -698,7 +604,7 @@ gitlink-cli/
|
|||
|
||||
## 文档
|
||||
|
||||
- [Skills 使用指南](./skills/README.md) — AI Agent Skills 详细说明
|
||||
- [Skills 使用指南](skills/README.md) — AI Agent Skills 详细说明
|
||||
- [设计文档](doc/design.md) — 架构设计和开发计划
|
||||
|
||||
## 常见问题
|
||||
|
|
@ -761,7 +667,7 @@ gitlink-cli 使用 Windows Credential Manager 安全存储 Token。如果 Creden
|
|||
|
||||
### Q: 如何查看完整的 API 参考?
|
||||
|
||||
查看 [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md)
|
||||
查看 [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md)
|
||||
|
||||
## 许可证
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# Meta and Attachment Shortcuts
|
||||
|
||||
## Summary
|
||||
|
||||
This change adds high-level shortcuts for GitLink OpenAPI endpoints that were previously only reachable through Raw API:
|
||||
|
||||
- `meta +licenses` → `GET /api/licenses.json`
|
||||
- `meta +ignores` → `GET /api/ignores.json`
|
||||
- `attachment +upload` → `POST /api/attachments.json`
|
||||
- `attachment +delete` → `DELETE /api/attachments/{uuid}.json`
|
||||
|
||||
## User Value
|
||||
|
||||
- Maintainers can query license and `.gitignore` templates before creating repositories.
|
||||
- Agents can upload files once, capture the returned attachment UUID/URL, and reuse it in Issue/PR/comment workflows.
|
||||
- Destructive attachment deletion supports `--dry-run` to preview the request before remote mutation.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests cover query parameters, multipart upload fields, dry-run behavior, missing local files, and deletion.
|
||||
- README and Skill docs include command examples and Agent safety guidance.
|
||||
|
|
@ -1,90 +1,183 @@
|
|||
package attachment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/output"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// Shortcuts returns attachment upload/delete shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "upload",
|
||||
Description: "Upload an attachment",
|
||||
Description: "Upload an attachment file",
|
||||
Flags: []common.Flag{
|
||||
{Name: "file", Short: "f", Usage: "File path to upload", Required: true},
|
||||
{Name: "file", Short: "f", Usage: "Local file path to upload", Required: true},
|
||||
{Name: "description", Short: "d", Usage: "Attachment description"},
|
||||
{Name: "container-id", Usage: "Container model ID"},
|
||||
{Name: "container-type", Usage: "Container model type"},
|
||||
{Name: "container-id", Usage: "Optional container model ID"},
|
||||
{Name: "container-type", Usage: "Optional container model type"},
|
||||
{Name: "dry-run", Usage: "Preview the multipart fields without uploading the file", Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runUploadAttachment,
|
||||
Run: runUpload,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Description: "Delete an attachment",
|
||||
Description: "Delete an attachment by UUID",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Attachment UUID", Required: true},
|
||||
{Name: "uuid", Short: "u", Usage: "Attachment UUID", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview the delete request without deleting the attachment", Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runDeleteAttachment,
|
||||
Run: runDelete,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runUploadAttachment(ctx *common.RuntimeContext) error {
|
||||
func runUpload(ctx *common.RuntimeContext) error {
|
||||
filePath, err := ctx.RequireArg("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Stat(filePath)
|
||||
fields := attachmentFields(ctx)
|
||||
if parseBool(ctx.Arg("dry-run")) {
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"dry_run": true,
|
||||
"action": "upload_attachment",
|
||||
"method": "POST",
|
||||
"path": "/attachments",
|
||||
"file": filePath,
|
||||
"filename": filepath.Base(filePath),
|
||||
"fields": fields,
|
||||
})
|
||||
}
|
||||
env, err := uploadAttachment(ctx, filePath, fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat file: %w", err)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("file path points to a directory: %s", filePath)
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runDelete(ctx *common.RuntimeContext) error {
|
||||
uuid, err := ctx.RequireArg("uuid")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("/attachments/%s", uuid)
|
||||
if parseBool(ctx.Arg("dry-run")) {
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"dry_run": true,
|
||||
"action": "delete_attachment",
|
||||
"method": "DELETE",
|
||||
"path": path,
|
||||
})
|
||||
}
|
||||
env, err := ctx.CallAPI("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func attachmentFields(ctx *common.RuntimeContext) map[string]string {
|
||||
fields := map[string]string{}
|
||||
if v := ctx.Arg("description"); v != "" {
|
||||
fields["description"] = v
|
||||
for _, name := range []string{"description", "container-id", "container-type"} {
|
||||
if value := ctx.Arg(name); value != "" {
|
||||
fields[apiFieldName(name)] = value
|
||||
}
|
||||
}
|
||||
if v := ctx.Arg("container-id"); v != "" {
|
||||
fields["container_id"] = v
|
||||
}
|
||||
if v := ctx.Arg("container-type"); v != "" {
|
||||
fields["container_type"] = v
|
||||
}
|
||||
|
||||
env, err := ctx.PostMultipart("/attachments", "file", filePath, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data, ok := env.Data.(map[string]interface{}); ok {
|
||||
data["filename"] = filepath.Base(filePath)
|
||||
}
|
||||
|
||||
return ctx.Output(env)
|
||||
return fields
|
||||
}
|
||||
|
||||
func runDeleteAttachment(ctx *common.RuntimeContext) error {
|
||||
id, err := ctx.RequireArg("id")
|
||||
func apiFieldName(flagName string) string {
|
||||
switch flagName {
|
||||
case "container-id":
|
||||
return "container_id"
|
||||
case "container-type":
|
||||
return "container_type"
|
||||
default:
|
||||
return flagName
|
||||
}
|
||||
}
|
||||
|
||||
func uploadAttachment(ctx *common.RuntimeContext, filePath string, fields map[string]string) (*output.Envelope, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("open attachment file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create multipart file field: %w", err)
|
||||
}
|
||||
if _, err := io.Copy(part, file); err != nil {
|
||||
return nil, fmt.Errorf("read attachment file: %w", err)
|
||||
}
|
||||
for key, value := range fields {
|
||||
if err := writer.WriteField(key, value); err != nil {
|
||||
return nil, fmt.Errorf("write multipart field %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, fmt.Errorf("close multipart writer: %w", err)
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/attachments/%s", id), nil)
|
||||
url := apiURL(ctx.Client.BaseURL, "/attachments")
|
||||
req, err := http.NewRequest("POST", url, &body)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if env == nil {
|
||||
return output.Print(output.SuccessEnvelope(map[string]interface{}{
|
||||
"id": id,
|
||||
"deleted": true,
|
||||
}, nil), ctx.Format)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
httpClient := ctx.Client.HTTP
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
return ctx.Output(env)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(respData)))
|
||||
}
|
||||
|
||||
var parsed interface{}
|
||||
if err := json.Unmarshal(respData, &parsed); err != nil {
|
||||
return output.SuccessEnvelope(string(respData), nil), nil
|
||||
}
|
||||
if data, ok := parsed.(map[string]interface{}); ok {
|
||||
if status, ok := data["status"].(float64); ok && status != 0 && status != 1 && status != 200 {
|
||||
message, _ := data["message"].(string)
|
||||
return nil, fmt.Errorf("[%v] %s", status, message)
|
||||
}
|
||||
}
|
||||
return output.SuccessEnvelope(parsed, nil), nil
|
||||
}
|
||||
|
||||
func apiURL(baseURL, path string) string {
|
||||
fullPath := path
|
||||
if !strings.HasSuffix(fullPath, ".json") {
|
||||
fullPath += ".json"
|
||||
}
|
||||
return strings.TrimRight(baseURL, "/") + fullPath
|
||||
}
|
||||
|
||||
func parseBool(value string) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(value), "true")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,128 @@
|
|||
package attachment
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func TestAttachmentUploadSendsMultipartForm(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
filePath := filepath.Join(tempDir, "sample.txt")
|
||||
if err := os.WriteFile(filePath, []byte("hello attachment"), 0o644); err != nil {
|
||||
t.Fatalf("write temp file: %v", err)
|
||||
func TestAttachmentUploadDryRunDoesNotCallAPI(t *testing.T) {
|
||||
server := newAttachmentTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("dry-run should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runAttachmentShortcut(t, server, "upload", map[string]string{
|
||||
"file": filepath.Join(t.TempDir(), "missing.txt"),
|
||||
"description": "design screenshot",
|
||||
"container-id": "123",
|
||||
"container-type": "Issue",
|
||||
"dry-run": "true",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("upload dry-run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachmentUploadMultipartPayload(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
filePath := filepath.Join(tmpDir, "note.txt")
|
||||
if err := os.WriteFile(filePath, []byte("hello attachment"), 0600); err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
|
||||
called := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/attachments.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
server := newAttachmentTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertAttachmentRequest(t, r, "POST", "/attachments.json")
|
||||
if got := r.Header.Get("Content-Type"); !strings.HasPrefix(got, "multipart/form-data;") {
|
||||
t.Fatalf("got content-type %q, want multipart/form-data", got)
|
||||
}
|
||||
called = true
|
||||
if err := r.ParseMultipartForm(1 << 20); err != nil {
|
||||
t.Fatalf("parse multipart: %v", err)
|
||||
}
|
||||
if got := r.FormValue("description"); got != "release asset" {
|
||||
t.Fatalf("description = %q, want %q", got, "release asset")
|
||||
}
|
||||
if got := r.FormValue("container_id"); got != "42" {
|
||||
t.Fatalf("container_id = %q, want %q", got, "42")
|
||||
}
|
||||
if got := r.FormValue("container_type"); got != "VersionRelease" {
|
||||
t.Fatalf("container_type = %q, want %q", got, "VersionRelease")
|
||||
t.Fatalf("failed to parse multipart form: %v", err)
|
||||
}
|
||||
assertFormValue(t, r.MultipartForm, "description", "design screenshot")
|
||||
assertFormValue(t, r.MultipartForm, "container_id", "123")
|
||||
assertFormValue(t, r.MultipartForm, "container_type", "Issue")
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
t.Fatalf("read form file: %v", err)
|
||||
t.Fatalf("file field missing: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
if header.Filename != "sample.txt" {
|
||||
t.Fatalf("filename = %q, want %q", header.Filename, "sample.txt")
|
||||
if header.Filename != "note.txt" {
|
||||
t.Fatalf("got filename %q, want note.txt", header.Filename)
|
||||
}
|
||||
content, err := io.ReadAll(file)
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatalf("read uploaded file: %v", err)
|
||||
t.Fatalf("failed to read uploaded file: %v", err)
|
||||
}
|
||||
if string(content) != "hello attachment" {
|
||||
t.Fatalf("content = %q, want %q", string(content), "hello attachment")
|
||||
if string(data) != "hello attachment" {
|
||||
t.Fatalf("got file content %q", string(data))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"id":"att-1","title":"sample.txt","url":"https://example.com/a/att-1"}`))
|
||||
}))
|
||||
writeAttachmentJSON(t, w, map[string]interface{}{
|
||||
"id": "uuid-1",
|
||||
"title": "note.txt",
|
||||
"filesize": "16 Bytes",
|
||||
"is_pdf": false,
|
||||
"url": "/api/attachments/uuid-1",
|
||||
"content_type": "text/plain",
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runAttachmentShortcut(t, server, "upload", map[string]string{
|
||||
"file": filePath,
|
||||
"description": "release asset",
|
||||
"container-id": "42",
|
||||
"container-type": "VersionRelease",
|
||||
"description": "design screenshot",
|
||||
"container-id": "123",
|
||||
"container-type": "Issue",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("upload shortcut failed: %v", err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("upload endpoint was not called")
|
||||
}
|
||||
|
||||
func TestAttachmentUploadMissingFile(t *testing.T) {
|
||||
server := newAttachmentTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("missing file should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runAttachmentShortcut(t, server, "upload", map[string]string{"file": filepath.Join(t.TempDir(), "missing.txt")})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing file to return an error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "open attachment file") {
|
||||
t.Fatalf("got error %q, want open attachment file", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachmentDeleteCallsAPI(t *testing.T) {
|
||||
called := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "DELETE" || r.URL.Path != "/attachments/att-1.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
called = true
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"status":0,"message":"deleted"}`))
|
||||
}))
|
||||
func TestAttachmentDelete(t *testing.T) {
|
||||
server := newAttachmentTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertAttachmentRequest(t, r, "DELETE", "/attachments/uuid-1.json")
|
||||
writeAttachmentJSON(t, w, map[string]interface{}{"status": 0, "message": "删除成功"})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runAttachmentShortcut(t, server, "delete", map[string]string{
|
||||
"id": "att-1",
|
||||
})
|
||||
if err != nil {
|
||||
if err := runAttachmentShortcut(t, server, "delete", map[string]string{"uuid": "uuid-1"}); err != nil {
|
||||
t.Fatalf("delete shortcut failed: %v", err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("delete endpoint was not called")
|
||||
}
|
||||
|
||||
func TestAttachmentDeleteDryRunDoesNotCallAPI(t *testing.T) {
|
||||
server := newAttachmentTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("dry-run should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runAttachmentShortcut(t, server, "delete", map[string]string{"uuid": "uuid-1", "dry-run": "true"}); err != nil {
|
||||
t.Fatalf("delete dry-run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +137,9 @@ func runAttachmentShortcut(t *testing.T, server *httptest.Server, name string, a
|
|||
Format: "json",
|
||||
Args: args,
|
||||
}
|
||||
if ctx.Args == nil {
|
||||
ctx.Args = map[string]string{}
|
||||
}
|
||||
return shortcut.Run(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -118,3 +153,31 @@ func findAttachmentShortcut(t *testing.T, name string) *common.Shortcut {
|
|||
t.Fatalf("shortcut %q not found", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newAttachmentTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func assertAttachmentRequest(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 assertFormValue(t *testing.T, form *multipart.Form, key, want string) {
|
||||
t.Helper()
|
||||
values := form.Value[key]
|
||||
if len(values) != 1 || values[0] != want {
|
||||
t.Fatalf("got form field %s=%v, want %q", key, values, want)
|
||||
}
|
||||
}
|
||||
|
||||
func writeAttachmentJSON(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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// Shortcuts returns read-only metadata lookup shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "licenses",
|
||||
Description: "List repository license templates",
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Filter license templates by name"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
q := url.Values{}
|
||||
if name := ctx.Arg("name"); name != "" {
|
||||
q.Set("name", name)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/licenses", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ignores",
|
||||
Description: "List .gitignore templates",
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Filter ignore templates by name"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
q := url.Values{}
|
||||
if name := ctx.Arg("name"); name != "" {
|
||||
q.Set("name", name)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/ignores", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package meta
|
||||
|
||||
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 TestMetaLicensesWithNameFilter(t *testing.T) {
|
||||
server := newMetaTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMetaRequest(t, r, "GET", "/licenses.json")
|
||||
if got := r.URL.Query().Get("name"); got != "MIT" {
|
||||
t.Fatalf("got name query %q, want MIT", got)
|
||||
}
|
||||
writeMetaJSON(t, w, map[string]interface{}{
|
||||
"licenses": []map[string]interface{}{{"id": 1, "name": "MIT"}},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runMetaShortcut(t, server, "licenses", map[string]string{"name": "MIT"}); err != nil {
|
||||
t.Fatalf("licenses shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaIgnoresWithNameFilter(t *testing.T) {
|
||||
server := newMetaTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMetaRequest(t, r, "GET", "/ignores.json")
|
||||
if got := r.URL.Query().Get("name"); got != "Go" {
|
||||
t.Fatalf("got name query %q, want Go", got)
|
||||
}
|
||||
writeMetaJSON(t, w, map[string]interface{}{
|
||||
"ignores": []map[string]interface{}{{"id": 2, "name": "Go"}},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runMetaShortcut(t, server, "ignores", map[string]string{"name": "Go"}); err != nil {
|
||||
t.Fatalf("ignores shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runMetaShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
shortcut := findMetaShortcut(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 findMetaShortcut(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 newMetaTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func assertMetaRequest(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 writeMetaJSON(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)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,27 +4,24 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/attachment"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/branch"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/ci"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/compare"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/dataset"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/health"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/ignore"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/issue"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/label"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/license"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/member"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/meta"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/milestone"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/org"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/pipeline"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/pm"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/pr"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/profile"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/release"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/repo"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/search"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/template"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/user"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/webhook"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/workflow"
|
||||
|
|
@ -37,57 +34,49 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
tr = translators[0]
|
||||
}
|
||||
groups := map[string][]*common.Shortcut{
|
||||
"repo": repo.Shortcuts(tr),
|
||||
"issue": issue.Shortcuts(tr),
|
||||
"label": label.Shortcuts(),
|
||||
"license": license.Shortcuts(),
|
||||
"member": member.Shortcuts(),
|
||||
"milestone": milestone.Shortcuts(),
|
||||
"pipeline": pipeline.Shortcuts(),
|
||||
"pm": pm.Shortcuts(tr),
|
||||
"pr": pr.Shortcuts(tr),
|
||||
"profile": profile.Shortcuts(tr),
|
||||
"release": release.Shortcuts(tr),
|
||||
"branch": branch.Shortcuts(tr),
|
||||
"org": org.Shortcuts(tr),
|
||||
"user": user.Shortcuts(tr),
|
||||
"search": search.Shortcuts(tr),
|
||||
"ci": ci.Shortcuts(tr),
|
||||
"compare": compare.Shortcuts(),
|
||||
"dataset": dataset.Shortcuts(tr),
|
||||
"webhook": webhook.Shortcuts(tr),
|
||||
"health": health.Shortcuts(tr),
|
||||
"ignore": ignore.Shortcuts(),
|
||||
"template": template.Shortcuts(),
|
||||
"workflow": workflow.Shortcuts(),
|
||||
"template": template.Shortcuts(tr),
|
||||
"attachment": attachment.Shortcuts(),
|
||||
"repo": repo.Shortcuts(tr),
|
||||
"issue": issue.Shortcuts(tr),
|
||||
"label": label.Shortcuts(),
|
||||
"license": license.Shortcuts(),
|
||||
"member": member.Shortcuts(),
|
||||
"meta": meta.Shortcuts(),
|
||||
"milestone": milestone.Shortcuts(),
|
||||
"pipeline": pipeline.Shortcuts(),
|
||||
"pr": pr.Shortcuts(tr),
|
||||
"release": release.Shortcuts(tr),
|
||||
"branch": branch.Shortcuts(tr),
|
||||
"org": org.Shortcuts(tr),
|
||||
"user": user.Shortcuts(tr),
|
||||
"search": search.Shortcuts(tr),
|
||||
"ci": ci.Shortcuts(tr),
|
||||
"compare": compare.Shortcuts(),
|
||||
"webhook": webhook.Shortcuts(tr),
|
||||
"health": health.Shortcuts(tr),
|
||||
"workflow": workflow.Shortcuts(),
|
||||
}
|
||||
|
||||
descriptions := map[string]string{
|
||||
"repo": tr.T("cmd.repo.short"),
|
||||
"issue": tr.T("cmd.issue.short"),
|
||||
"label": "Issue label operations",
|
||||
"license": "License operations",
|
||||
"member": "Repository member operations",
|
||||
"milestone": "Milestone operations",
|
||||
"pipeline": "Pipeline operations",
|
||||
"pm": tr.T("cmd.pm.short"),
|
||||
"pr": tr.T("cmd.pr.short"),
|
||||
"profile": tr.T("cmd.profile.short"),
|
||||
"release": tr.T("cmd.release.short"),
|
||||
"branch": tr.T("cmd.branch.short"),
|
||||
"org": tr.T("cmd.org.short"),
|
||||
"user": tr.T("cmd.user.short"),
|
||||
"search": tr.T("cmd.search.short"),
|
||||
"ci": tr.T("cmd.ci.short"),
|
||||
"compare": "Compare branches, tags, or commits",
|
||||
"dataset": tr.T("cmd.dataset.short"),
|
||||
"webhook": tr.T("cmd.webhook.short"),
|
||||
"health": "Project health data collection",
|
||||
"ignore": tr.T("cmd.ignore.short"),
|
||||
"template": "项目模板操作",
|
||||
"workflow": "AI agent workflow analysis",
|
||||
"template": tr.T("cmd.template.short"),
|
||||
"attachment": "Attachment upload and delete operations",
|
||||
"repo": tr.T("cmd.repo.short"),
|
||||
"issue": tr.T("cmd.issue.short"),
|
||||
"label": "Issue label operations",
|
||||
"license": "License operations",
|
||||
"member": "Repository member operations",
|
||||
"meta": "GitLink metadata template operations",
|
||||
"milestone": "Milestone operations",
|
||||
"pipeline": "Pipeline operations",
|
||||
"pr": tr.T("cmd.pr.short"),
|
||||
"release": tr.T("cmd.release.short"),
|
||||
"branch": tr.T("cmd.branch.short"),
|
||||
"org": tr.T("cmd.org.short"),
|
||||
"user": tr.T("cmd.user.short"),
|
||||
"search": tr.T("cmd.search.short"),
|
||||
"ci": tr.T("cmd.ci.short"),
|
||||
"compare": "Compare branches, tags, or commits",
|
||||
"webhook": tr.T("cmd.webhook.short"),
|
||||
"health": "Project health data collection",
|
||||
"workflow": "AI agent workflow analysis",
|
||||
}
|
||||
|
||||
for name, shortcuts := range groups {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ func TestRegisterAll(t *testing.T) {
|
|||
RegisterAll(root)
|
||||
|
||||
expectedGroups := []string{
|
||||
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
|
||||
"repo", "issue", "label", "license", "pr", "release", "branch",
|
||||
"org", "user", "search", "ci", "workflow",
|
||||
"compare", "member", "milestone", "pipeline", "pm", "webhook",
|
||||
"dataset", "health", "ignore", "wiki",
|
||||
"compare", "member", "milestone", "pipeline", "webhook",
|
||||
"health", "attachment", "meta",
|
||||
}
|
||||
|
||||
groupSet := map[string]bool{}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ skills/
|
|||
│ ├── SKILL.md # 搜索操作指南
|
||||
│ └── examples/
|
||||
│ └── search-workflow.md # 搜索工作流
|
||||
├── gitlink-attachment/ # 附件管理
|
||||
│ └── SKILL.md # 附件上传与删除指南
|
||||
├── gitlink-meta/ # 公开元数据查询
|
||||
│ └── SKILL.md # 许可证和 .gitignore 模板查询指南
|
||||
├── gitlink-user/ # 用户管理
|
||||
│ └── SKILL.md # 用户操作指南
|
||||
├── gitlink-org/ # 组织管理
|
||||
|
|
@ -150,6 +154,8 @@ skills/
|
|||
| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members` |
|
||||
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
|
||||
| **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` |
|
||||
| **gitlink-attachment** | 附件管理 | `attachment +upload`, `attachment +delete` |
|
||||
| **gitlink-meta** | 公开元数据查询 | `meta +licenses`, `meta +ignores` |
|
||||
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
|
||||
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
|
||||
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
|
||||
|
|
@ -226,6 +232,20 @@ gitlink-cli org +info -i Gitlink
|
|||
|
||||
详见: [gitlink-search/examples/search-workflow.md](gitlink-search/examples/search-workflow.md)
|
||||
|
||||
### 场景 5:附件和模板元数据
|
||||
|
||||
```bash
|
||||
# 查询创建仓库时可用的模板元数据
|
||||
gitlink-cli meta +licenses --name MIT
|
||||
gitlink-cli meta +ignores --name Go
|
||||
|
||||
# 先 dry-run,再上传附件
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "复现截图" --dry-run
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "复现截图"
|
||||
```
|
||||
|
||||
详见: [gitlink-attachment/SKILL.md](gitlink-attachment/SKILL.md)、[gitlink-meta/SKILL.md](gitlink-meta/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
## 📚 文档导航
|
||||
|
|
@ -249,10 +269,12 @@ gitlink-cli org +info -i Gitlink
|
|||
- [gitlink-pr/SKILL.md](gitlink-pr/SKILL.md) - PR 命令
|
||||
- [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md) - Issue 工作流
|
||||
|
||||
**发布和搜索**:
|
||||
**发布、搜索和附件**:
|
||||
- [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-attachment/SKILL.md](gitlink-attachment/SKILL.md) - 附件命令
|
||||
- [gitlink-meta/SKILL.md](gitlink-meta/SKILL.md) - 公开元数据命令
|
||||
|
||||
**组织和用户**:
|
||||
- [gitlink-org/SKILL.md](gitlink-org/SKILL.md) - 组织命令
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
name: gitlink-attachment
|
||||
version: 1.0.0
|
||||
description: "附件管理:上传文件、删除附件,适用于 Issue/PR/数据集等需要附件 ID 的工作流。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli attachment --help"
|
||||
---
|
||||
|
||||
# gitlink-attachment(附件管理)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 上传/删除附件属于写操作。执行真实写入前,优先使用 `--dry-run` 预览并确认用户意图。**
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Shortcut | 说明 | 操作类型 |
|
||||
|----------|------|----------|
|
||||
| `attachment +upload` | 上传本地文件,返回附件 UUID/URL 等信息 | ⚠️ Write Operation |
|
||||
| `attachment +delete` | 按 UUID 删除附件 | 🔴 Destructive Operation |
|
||||
|
||||
## 参数参考
|
||||
|
||||
### attachment +upload
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--file, -f` | 是 | 本地文件路径 |
|
||||
| `--description, -d` | 否 | 附件描述 |
|
||||
| `--container-id` | 否 | 归属模型 ID,例如 Issue/PR/数据集记录 ID |
|
||||
| `--container-type` | 否 | 归属模型类型 |
|
||||
| `--dry-run` | 否 | 只预览 multipart 字段,不上传文件 |
|
||||
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
|
||||
|
||||
### attachment +delete
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--uuid, -u` | 是 | 附件 UUID |
|
||||
| `--dry-run` | 否 | 只预览删除请求,不删除附件 |
|
||||
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
|
||||
|
||||
## 使用示例
|
||||
|
||||
```bash
|
||||
# 预览上传,不修改线上数据
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "复现截图" --dry-run
|
||||
|
||||
# 上传附件
|
||||
gitlink-cli attachment +upload --file screenshot.png --description "复现截图"
|
||||
|
||||
# 上传并绑定到业务对象
|
||||
gitlink-cli attachment +upload --file design.pdf \
|
||||
--description "设计文档" \
|
||||
--container-id 123 \
|
||||
--container-type Issue
|
||||
|
||||
# 预览删除
|
||||
gitlink-cli attachment +delete --uuid f5838d8f-451b-4793-a0f2-0278430e8207 --dry-run
|
||||
|
||||
# 删除附件
|
||||
gitlink-cli attachment +delete --uuid f5838d8f-451b-4793-a0f2-0278430e8207
|
||||
```
|
||||
|
||||
## Agent 工作流建议
|
||||
|
||||
1. 确认用户要上传或删除的文件/附件 UUID。
|
||||
2. 写操作先执行 `--dry-run --format json`,展示将要调用的方法、路径和字段。
|
||||
3. 用户确认后再执行真实命令。
|
||||
4. 对上传结果,保存返回的 `id`/`url`,后续可作为 Issue、评论或数据集附件引用。
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证、全局参数、安全规则
|
||||
- GitLink OpenAPI:`POST /api/attachments.json`、`DELETE /api/attachments/{uuid}.json`
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
name: gitlink-meta
|
||||
version: 1.0.0
|
||||
description: "公开元数据查询:查询 GitLink 许可证模板和 .gitignore 模板。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli meta --help"
|
||||
---
|
||||
|
||||
# gitlink-meta(公开元数据查询)
|
||||
|
||||
**CRITICAL — 开始前建议阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、全局参数和输出格式说明。**
|
||||
|
||||
`meta` 命令只读取公开模板数据,不修改远端资源,适合创建仓库前查询可用的许可证和 `.gitignore` 模板。
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Shortcut | 说明 | 操作类型 |
|
||||
|----------|------|----------|
|
||||
| `meta +licenses` | 查询许可证模板列表 | Read |
|
||||
| `meta +ignores` | 查询 `.gitignore` 模板列表 | Read |
|
||||
|
||||
## 参数参考
|
||||
|
||||
| 命令 | 参数 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `meta +licenses` | `--name, -n` | 否 | 按许可证名称过滤,例如 `MIT` |
|
||||
| `meta +ignores` | `--name, -n` | 否 | 按模板名称过滤,例如 `Go` |
|
||||
| 两者 | `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
|
||||
|
||||
## 使用示例
|
||||
|
||||
```bash
|
||||
# 查询许可证模板
|
||||
gitlink-cli meta +licenses --name MIT --format json
|
||||
|
||||
# 查询 .gitignore 模板
|
||||
gitlink-cli meta +ignores --name Go --format json
|
||||
|
||||
# 列出全部模板
|
||||
gitlink-cli meta +licenses
|
||||
gitlink-cli meta +ignores
|
||||
```
|
||||
|
||||
## Agent 工作流建议
|
||||
|
||||
1. 创建仓库前先用 `meta +licenses` / `meta +ignores` 查询模板名称或 ID。
|
||||
2. 将查询结果与用户需求对齐,例如开源许可证选择、语言模板选择。
|
||||
3. 后续再调用仓库创建或更新命令,减少用户手动查网页的成本。
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证、全局参数、输出格式
|
||||
- GitLink OpenAPI:`GET /api/licenses.json`、`GET /api/ignores.json`
|
||||
Loading…
Reference in New Issue