diff --git a/README.md b/README.md index 0d18422..9c8549e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Go Version](https://img.shields.io/badge/Go-1.26%2B-blue.svg)](https://golang.org) [![npm version](https://img.shields.io/npm/v/@gitlink-ai/cli.svg)](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 jiangtx
jiangtx -
- luwanzhou -
luwanzhou -
-
- whale_hihihi -
whale_hihihi -
## 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 --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 --dry-run -gitlink-cli dataset +delete-attachment --owner me --repo proj --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 diff --git a/README.zh-CN.md b/README.zh-CN.md index 28ef7fa..bb5bb88 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -5,7 +5,7 @@ [![Go Version](https://img.shields.io/badge/Go-1.26%2B-blue.svg)](https://golang.org) [![npm version](https://img.shields.io/npm/v/@gitlink-ai/cli.svg)](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 @@ jiangtx
jiangtx -
- luwanzhou -
luwanzhou -
-
- whale_hihihi -
whale_hihihi -
## 为什么选择 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 --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 --dry-run -gitlink-cli dataset +delete-attachment --owner me --repo proj --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) ## 许可证 diff --git a/doc/changes/meta-attachment-shortcuts.md b/doc/changes/meta-attachment-shortcuts.md new file mode 100644 index 0000000..7468160 --- /dev/null +++ b/doc/changes/meta-attachment-shortcuts.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. diff --git a/shortcuts/attachment/attachment.go b/shortcuts/attachment/attachment.go index 4d39340..65fdc01 100644 --- a/shortcuts/attachment/attachment.go +++ b/shortcuts/attachment/attachment.go @@ -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") } diff --git a/shortcuts/attachment/attachment_test.go b/shortcuts/attachment/attachment_test.go index 4b378f2..27c24fc 100644 --- a/shortcuts/attachment/attachment_test.go +++ b/shortcuts/attachment/attachment_test.go @@ -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) + } +} diff --git a/shortcuts/meta/meta.go b/shortcuts/meta/meta.go new file mode 100644 index 0000000..5491f81 --- /dev/null +++ b/shortcuts/meta/meta.go @@ -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) + }, + }, + } +} diff --git a/shortcuts/meta/meta_test.go b/shortcuts/meta/meta_test.go new file mode 100644 index 0000000..63bb541 --- /dev/null +++ b/shortcuts/meta/meta_test.go @@ -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) + } +} diff --git a/shortcuts/register.go b/shortcuts/register.go index 6145ac8..f0d8935 100644 --- a/shortcuts/register.go +++ b/shortcuts/register.go @@ -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 { diff --git a/shortcuts/register_test.go b/shortcuts/register_test.go index 4c14316..578ea5f 100644 --- a/shortcuts/register_test.go +++ b/shortcuts/register_test.go @@ -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{} diff --git a/skills/README.md b/skills/README.md index a36e9ff..ed6e98f 100644 --- a/skills/README.md +++ b/skills/README.md @@ -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) - 组织命令 diff --git a/skills/gitlink-attachment/SKILL.md b/skills/gitlink-attachment/SKILL.md new file mode 100644 index 0000000..254fa0c --- /dev/null +++ b/skills/gitlink-attachment/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` diff --git a/skills/gitlink-meta/SKILL.md b/skills/gitlink-meta/SKILL.md new file mode 100644 index 0000000..3b08483 --- /dev/null +++ b/skills/gitlink-meta/SKILL.md @@ -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`