Merge PR #50: feat: add wiki shortcut group
# Conflicts: # README.md # README.zh-CN.md # doc/changes/wiki-shortcut.md # internal/i18n/locales/en-US.json # internal/i18n/locales/zh-CN.json # shortcuts/register_test.go # shortcuts/wiki/wiki.go # shortcuts/wiki/wiki_test.go # skills/gitlink-wiki/SKILL.md
This commit is contained in:
commit
dd2e803980
138
README.md
138
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/).
|
||||
The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, wiki pages, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/).
|
||||
|
||||
**[中文文档](./README.zh-CN.md)**
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
|||
## Why gitlink-cli?
|
||||
|
||||
- **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, Member, Branch, Release, CI, Pipeline, Org, Search, and User workflows are covered by high-level commands
|
||||
- **Wide Coverage** — Repository, Wiki, Issue, PR, Webhook, 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`
|
||||
|
|
@ -96,7 +96,7 @@ 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 |
|
||||
| ⭐ Reaction | Follow, unfollow, like, unlike, list watchers and stargazers |
|
||||
| 📚 Wiki | List, view, create, update, and delete wiki pages |
|
||||
| 🐛 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 |
|
||||
|
|
@ -107,7 +107,6 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
|||
| 🔧 CI | View builds, logs, CI/CD operations |
|
||||
| ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs |
|
||||
| 🔔 Webhook | Manage repo webhooks and test deliveries |
|
||||
| 📖 Wiki | List, view, create, update, delete, and export wiki pages |
|
||||
| 🔍 Search | Search repositories, users |
|
||||
| 👤 User | View user profiles and info |
|
||||
| 📋 PM | Sprint management, kanban boards, weekly reports |
|
||||
|
|
@ -248,22 +247,25 @@ gitlink-cli repo +create -n my-project -d "Project description"
|
|||
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
||||
```
|
||||
|
||||
### Repository Reactions
|
||||
### Wiki Management
|
||||
|
||||
```bash
|
||||
# List repository watchers
|
||||
gitlink-cli reaction +watchers --owner Gitlink --repo forgeplus
|
||||
# List wiki pages
|
||||
gitlink-cli wiki +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# List repository stargazers
|
||||
gitlink-cli reaction +stargazers --owner Gitlink --repo forgeplus
|
||||
# View a wiki page
|
||||
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --page Home
|
||||
|
||||
# Follow or unfollow a repository
|
||||
gitlink-cli reaction +follow --owner Gitlink --repo forgeplus
|
||||
gitlink-cli reaction +unfollow --owner Gitlink --repo forgeplus
|
||||
# Create a wiki page from inline content
|
||||
gitlink-cli wiki +create --owner Gitlink --repo forgeplus \
|
||||
--page Home --title Home --content "Welcome to the project wiki"
|
||||
|
||||
# Like or unlike a repository
|
||||
gitlink-cli reaction +like --owner Gitlink --repo forgeplus
|
||||
gitlink-cli reaction +unlike --owner Gitlink --repo forgeplus
|
||||
# Update a wiki page from a Markdown file
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus \
|
||||
--page Home --file docs/wiki-home.md --message "Update Home"
|
||||
|
||||
# Delete a wiki page
|
||||
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --page Home
|
||||
```
|
||||
|
||||
### Webhook Management
|
||||
|
|
@ -311,22 +313,6 @@ gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role develope
|
|||
# List issues
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# Fetch all pages automatically (works on all paginated list commands:
|
||||
# issue/pr/branch/release/milestone/org/repo/label/member/webhook/tag/commit +list,
|
||||
# issue +comments, repo +watchers/+stargazers/+forks, search +repos/+users)
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --all
|
||||
|
||||
# Commit history and single commit details
|
||||
gitlink-cli commit +list --owner Gitlink --repo forgeplus --ref develop --all
|
||||
gitlink-cli commit +view --owner Gitlink --repo forgeplus --sha <sha>
|
||||
|
||||
# Git tags
|
||||
gitlink-cli tag +list --owner Gitlink --repo forgeplus --all
|
||||
|
||||
# Show a single tag by name (falls back to a list scan when the
|
||||
# show endpoint's existence precheck misfires)
|
||||
gitlink-cli tag +view --owner Gitlink --repo forgeplus -n v4.0.0
|
||||
|
||||
# Create an issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" -b "Steps to reproduce..."
|
||||
|
||||
|
|
@ -594,14 +580,6 @@ gitlink-cli workflow +pr-summary --from shortcuts/workflow/testdata/pr_summary.j
|
|||
# Repository workflow report by read-only GitLink fetch
|
||||
gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --format markdown
|
||||
|
||||
# Optional full PR review attribution. This deep-fetches formal reviews and
|
||||
# PR-associated Issue journals for analyzed PRs, so keep it explicit.
|
||||
gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --include-pr-review-audit --format json > report.review-audit.json
|
||||
|
||||
# Limit analysis only when an intentional sample is needed.
|
||||
# By default, repo-report paginates through all open issues and pull requests.
|
||||
gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --issue-limit 20 --pr-limit 50 --format markdown
|
||||
|
||||
# Repository workflow report from a local JSON file
|
||||
gitlink-cli workflow +repo-report --from shortcuts/workflow/testdata/repo_report.json --format json
|
||||
```
|
||||
|
|
@ -619,86 +597,6 @@ Safety:
|
|||
- They do not depend on LLM APIs.
|
||||
- `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.
|
||||
- `workflow +repo-report --include-pr-review-audit` remains read-only. It treats formal review objects as authoritative review evidence and keeps submitter, reviewer, participant, and system journal activity separate.
|
||||
|
||||
### Feishu Collaboration Export
|
||||
|
||||
`feishu` turns `workflow +repo-report` JSON into Feishu collaboration outputs.
|
||||
|
||||
`workflow +repo-report` paginates through all open issues and pull requests by
|
||||
default. Feishu cards label these values as analyzed counts. Passing
|
||||
`--issue-limit` or `--pr-limit` intentionally limits the analysis and the
|
||||
resulting values must not be interpreted as repository totals.
|
||||
|
||||
Stable usage:
|
||||
|
||||
```bash
|
||||
gitlink-cli workflow +repo-report --owner "$GITLINK_OWNER" --repo "$GITLINK_REPO" --format json > report.json
|
||||
gitlink-cli workflow +repo-report --owner "$GITLINK_OWNER" --repo "$GITLINK_REPO" --include-pr-review-audit --format json > report.review-audit.json
|
||||
|
||||
gitlink-cli feishu +notify --from-workflow-json report.json --format json
|
||||
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
|
||||
gitlink-cli feishu +owner-digest --from-workflow-json report.review-audit.json --format table
|
||||
|
||||
gitlink-cli feishu +weekly-report --from-workflow-json report.json --format markdown
|
||||
gitlink-cli feishu +owner-digest --from-workflow-json report.json --format markdown
|
||||
gitlink-cli feishu +contributor-digest --from-workflow-json report.json --format markdown
|
||||
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
|
||||
gitlink-cli feishu +task-preview --from-workflow-json report.json --format markdown
|
||||
```
|
||||
|
||||
Experimental Open Platform usage:
|
||||
|
||||
```bash
|
||||
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url "$FEISHU_WIKI_URL" --send --format table
|
||||
gitlink-cli feishu +bitable-sync --from-workflow-json report.json --tables reports,issues,prs,tasks --send --format table
|
||||
gitlink-cli feishu +task-create --from-workflow-json report.json --send --format table
|
||||
```
|
||||
|
||||
GitLink write operations are not implemented in this branch. Feishu card buttons are navigation-only. Open Platform commands require explicit `--send` and a self-built app with resource permissions. Whether these experimental capabilities should be enabled in official deployments is left to GitLink maintainers and deployment administrators.
|
||||
|
||||
Details:
|
||||
|
||||
- [Feishu integration](./docs/feishu-integration.md)
|
||||
- [Feishu capability layers](./docs/FEISHU_CAPABILITY_LAYERS.md)
|
||||
- [Feishu environment variables](./docs/FEISHU_ENVIRONMENT.md)
|
||||
- [Feishu permission matrix](./reports/FEISHU_PERMISSION_MATRIX.md)
|
||||
|
||||
Local setup and smoke testing:
|
||||
|
||||
```powershell
|
||||
.\scripts\feishu-gitlink-setup.ps1
|
||||
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
|
||||
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
|
||||
```
|
||||
|
||||
The setup script stores real values only in `.local/feishu-gitlink.env.ps1`, which is ignored.
|
||||
|
||||
### Wiki
|
||||
|
||||
`wiki` manages a repository's wiki pages. The numeric GitLink project ID is
|
||||
resolved from `--owner/--repo` automatically, or pass `--project-id`.
|
||||
|
||||
```bash
|
||||
# List and view wiki pages
|
||||
gitlink-cli wiki +list --owner Gitlink --repo gitlink-cli
|
||||
gitlink-cli wiki +view --owner Gitlink --repo gitlink-cli --page Home
|
||||
|
||||
# Create a page (content is base64-encoded automatically)
|
||||
gitlink-cli wiki +create --owner Gitlink --repo gitlink-cli --page Home --title Home --content "# Welcome"
|
||||
|
||||
# Create from a file
|
||||
gitlink-cli wiki +create --owner Gitlink --repo gitlink-cli --page Guide --content-file guide.md
|
||||
|
||||
# Update (content optional) and preview with --dry-run
|
||||
gitlink-cli wiki +update --owner Gitlink --repo gitlink-cli --page Home --title "Home Page" --dry-run
|
||||
|
||||
# Delete a page
|
||||
gitlink-cli wiki +delete --owner Gitlink --repo gitlink-cli --page Home
|
||||
|
||||
# Export the wiki (markdown, pdf, or html)
|
||||
gitlink-cli wiki +export --owner Gitlink --repo gitlink-cli --type markdown
|
||||
```
|
||||
|
||||
### Raw API
|
||||
|
||||
|
|
@ -762,7 +660,7 @@ See [skills/README.md](skills/README.md) for details.
|
|||
|-------|-------------|
|
||||
| `gitlink-shared` | Authentication, global parameters, safety rules, API notes |
|
||||
| `gitlink-repo` | Repository operations (create, view, delete, fork, insights, etc.) |
|
||||
| `gitlink-reaction` | Repository reactions (follow, like, watchers, stargazers) |
|
||||
| `gitlink-wiki` | Wiki operations (list, view, create, update, delete) |
|
||||
| `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 |
|
||||
|
|
|
|||
239
README.zh-CN.md
239
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**,覆盖仓库管理、Wiki、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
|
||||
- **广泛覆盖** — 仓库、Wiki、Issue、PR、Webhook、成员、分支、Release、CI、Pipeline、组织、搜索、用户等常用工作流均提供高层命令
|
||||
- **AI 友好 & 优化** — 每条命令都经过真实 Agent 测试,简洁参数、智能默认值、结构化输出
|
||||
- **跨平台** — macOS、Linux、Windows (x64/arm64) 全支持,`npm` 一条命令安装
|
||||
- **开源零门槛** — 木兰宽松许可证第2版(MulanPSL-2.0),`npm install` 即用
|
||||
|
|
@ -104,7 +96,8 @@
|
|||
| 分类 | 能力 |
|
||||
|------|------|
|
||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
|
||||
| 🐛 Issue | 创建、更新、关闭、批量关闭/更新/删除、评论 Issue |
|
||||
| 📚 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
|
||||
| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
|
||||
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
|
||||
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
|
||||
| 👥 成员 | 列出、添加、移除仓库成员,调整角色,生成和接受邀请链接 |
|
||||
|
|
@ -113,11 +106,8 @@
|
|||
| 🏢 组织 | 管理组织、成员、团队 |
|
||||
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
|
||||
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
|
||||
| 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
|
||||
| 🔍 搜索 | 搜索仓库、用户 |
|
||||
| 📊 数据集 | 按项目查询科研数据集 |
|
||||
| 👤 用户 | 查看用户资料和信息 |
|
||||
| 📊 画像 | 用户开发能力、角色定位、专业定位、近期活动、贡献热力图统计 |
|
||||
| 📋 项目管理 | Sprint 管理、看板、周报 |
|
||||
| 🤖 工作流 | AI 驱动的 Issue 分类、PR Review、Release Notes |
|
||||
|
||||
|
|
@ -268,6 +258,27 @@ gitlink-cli repo +create -n my-project -d "项目描述"
|
|||
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
||||
```
|
||||
|
||||
### Wiki 管理
|
||||
|
||||
```bash
|
||||
# 列出 Wiki 页面
|
||||
gitlink-cli wiki +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# 查看 Wiki 页面
|
||||
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --page Home
|
||||
|
||||
# 使用命令行内容创建 Wiki 页面
|
||||
gitlink-cli wiki +create --owner Gitlink --repo forgeplus \
|
||||
--page Home --title Home --content "欢迎来到项目 Wiki"
|
||||
|
||||
# 使用 Markdown 文件更新 Wiki 页面
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus \
|
||||
--page Home --file docs/wiki-home.md --message "更新 Home"
|
||||
|
||||
# 删除 Wiki 页面
|
||||
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --page Home
|
||||
```
|
||||
|
||||
### Webhook 管理
|
||||
|
||||
```bash
|
||||
|
|
@ -285,28 +296,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
|
||||
|
|
@ -335,18 +324,6 @@ gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role develope
|
|||
# 列出 Issue
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# 自动翻页拉取全部(适用于所有可分页 list 命令:
|
||||
# issue/pr/branch/release/milestone/org/repo/label/member/webhook/tag/commit +list、
|
||||
# issue +comments、repo +watchers/+stargazers/+forks、search +repos/+users)
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --all
|
||||
|
||||
# 提交历史与单个提交详情
|
||||
gitlink-cli commit +list --owner Gitlink --repo forgeplus --ref develop --all
|
||||
gitlink-cli commit +view --owner Gitlink --repo forgeplus --sha <sha>
|
||||
|
||||
# Git 标签
|
||||
gitlink-cli tag +list --owner Gitlink --repo forgeplus --all
|
||||
|
||||
# 创建 Issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: 登录失败" -b "复现步骤..."
|
||||
|
||||
|
|
@ -368,14 +345,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 "已修复"
|
||||
|
||||
|
|
@ -498,16 +467,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
|
||||
|
|
@ -518,143 +477,6 @@ 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 profile +role --user zhangsan
|
||||
gitlink-cli profile +major --user zhangsan
|
||||
|
||||
# 指定时间范围的开发能力(Unix 时间戳)
|
||||
gitlink-cli profile +ability --user zhangsan --start-time 1704067200 --end-time 1735689600
|
||||
|
||||
# 当前用户的近期活动(每日 疑修 / 合并请求 / 提交)
|
||||
gitlink-cli profile +activity
|
||||
|
||||
# 指定年份的贡献热力图
|
||||
gitlink-cli profile +contribution --user zhangsan --year 2025
|
||||
```
|
||||
|
||||
### 数据集
|
||||
|
||||
`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),待平台上线后即可生效。
|
||||
|
||||
### 飞书协作导出
|
||||
|
||||
`feishu` 将 `workflow +repo-report` JSON 转成飞书协作内容。
|
||||
|
||||
`workflow +repo-report` 默认分页读取并分析全部开放 Issue 和 PR。飞书卡片会把这些值明确标为“已分析数量”。只有显式传 `--issue-limit` 或 `--pr-limit` 时才会采样,此时结果不能解释为仓库总量。
|
||||
|
||||
#### 稳定层:自定义机器人通知
|
||||
|
||||
稳定层只依赖飞书群自定义机器人。它适合把 GitLink 项目状态、周报、Owner 摘要和贡献者摘要推送到群里。默认只预览,真实发送必须显式传 `--send`。
|
||||
|
||||
```bash
|
||||
gitlink-cli workflow +repo-report --owner "$GITLINK_OWNER" --repo "$GITLINK_REPO" --format json > report.json
|
||||
|
||||
# 仅在明确需要采样时设置上限
|
||||
gitlink-cli workflow +repo-report --owner "$GITLINK_OWNER" --repo "$GITLINK_REPO" --issue-limit 20 --pr-limit 50 --format json > report.sample.json
|
||||
|
||||
gitlink-cli feishu +notify --from-workflow-json report.json --format json
|
||||
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
|
||||
|
||||
gitlink-cli feishu +weekly-report --from-workflow-json report.json --format markdown
|
||||
gitlink-cli feishu +owner-digest --from-workflow-json report.json --format markdown
|
||||
gitlink-cli feishu +contributor-digest --from-workflow-json report.json --format markdown
|
||||
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
|
||||
gitlink-cli feishu +task-preview --from-workflow-json report.json --format markdown
|
||||
```
|
||||
|
||||
中文输出建议同时给 workflow 和 feishu 命令传 `--lang zh-CN`:
|
||||
|
||||
```bash
|
||||
gitlink-cli workflow +repo-report --owner "$GITLINK_OWNER" --repo "$GITLINK_REPO" --lang zh-CN --format json > report.zh-CN.json
|
||||
|
||||
gitlink-cli feishu +notify --from-workflow-json report.zh-CN.json --lang zh-CN --send --format table
|
||||
gitlink-cli feishu +owner-digest --from-workflow-json report.zh-CN.json --lang zh-CN --send --format table
|
||||
gitlink-cli feishu +contributor-digest --from-workflow-json report.zh-CN.json --lang zh-CN --send --format table
|
||||
```
|
||||
|
||||
#### 配置诊断层:先检查再写入
|
||||
|
||||
诊断命令用于降低飞书开放平台配置成本。默认只检查本地变量和目标配置;传 `--remote` 后会调用飞书只读/检查接口,例如获取 `tenant_access_token`、解析 Wiki node、搜索 Bitable sentinel `unique_key`。这些命令不会创建文档、不会写入多维表格、不会创建任务,也不会修改 GitLink。
|
||||
|
||||
```bash
|
||||
gitlink-cli feishu +app-check --format table
|
||||
gitlink-cli feishu +doc-check --remote --format table
|
||||
gitlink-cli feishu +bitable-check --tables reports,issues,prs,tasks --remote --format table
|
||||
gitlink-cli feishu +task-check --remote --format table
|
||||
```
|
||||
|
||||
#### 实验层:飞书开放平台写入
|
||||
|
||||
实验层使用飞书开放平台自建应用。当前已在测试企业中验证 DocX 追加、多维表格写入和飞书任务创建,但这部分不是零配置稳定能力。真实写入仍然必须显式传 `--send`,并且要求自建应用有对应 API scope 和目标资源权限。
|
||||
|
||||
```bash
|
||||
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url "$FEISHU_WIKI_URL" --send --format table
|
||||
gitlink-cli feishu +bitable-sync --from-workflow-json report.json --tables reports,issues,prs,tasks --send --format table
|
||||
gitlink-cli feishu +task-create --from-workflow-json report.json --send --format table
|
||||
```
|
||||
|
||||
为了完成端到端验证,测试企业里的自建应用授予了较宽的权限。正式部署时不建议照搬测试权限,应由维护者或管理员按命令实际需要开最小权限。
|
||||
|
||||
多维表格已完成两类真实验证:
|
||||
|
||||
- 单表多视图验证:把 `reports/issues/prs/contributors/tasks` 写入同一张测试表,证明字段和写入链路可用。
|
||||
- 独立表验证:拆成 `gitlink_reports`、`gitlink_issues`、`gitlink_prs`、`gitlink_contributors`、`gitlink_tasks` 五张表,分别写入 `1/5/2/1/7` 条记录,证明每类记录都能写入独立表。
|
||||
|
||||
#### 当前边界
|
||||
|
||||
本分支不实现 GitLink 写操作。飞书卡片按钮仅用于跳转。开放平台能力必须显式传 `--send`,并要求自建应用具备对应资源权限。是否在正式部署中启用这些实验能力,由 GitLink 维护者和部署管理员决定。
|
||||
|
||||
下一阶段再考虑:
|
||||
|
||||
- 飞书任务项目/分组归属。
|
||||
- 飞书任务执行者/关注人。
|
||||
- 飞书侧任务去重或搜索。
|
||||
- 多维表格自动建 Base、建表、建字段、建视图。
|
||||
- 飞书卡片回调和 GitLink 低风险写动作。
|
||||
|
||||
详细文档:
|
||||
|
||||
- [飞书集成](./docs/feishu-integration.md)
|
||||
- [飞书能力分层](./docs/FEISHU_CAPABILITY_LAYERS.md)
|
||||
- [飞书环境变量](./docs/FEISHU_ENVIRONMENT.md)
|
||||
- [飞书权限矩阵](./reports/FEISHU_PERMISSION_MATRIX.md)
|
||||
|
||||
本地配置和 smoke 测试:
|
||||
|
||||
```powershell
|
||||
.\scripts\feishu-gitlink-setup.ps1
|
||||
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
|
||||
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
|
||||
```
|
||||
|
||||
真实值只会写入被忽略的 `.local/feishu-gitlink.env.ps1`,不要提交。
|
||||
### Raw API
|
||||
|
||||
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:
|
||||
|
|
@ -711,13 +533,14 @@ 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-wiki` | Wiki 操作(列出、查看、创建、更新、删除) |
|
||||
| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
|
||||
| `gitlink-pr` | Pull Request 操作(创建、合并、Review 等) |
|
||||
| `gitlink-member` | 仓库成员与邀请链接管理 |
|
||||
| `gitlink-release` | 发布管理(创建、编辑、更新、查看、删除等) |
|
||||
|
|
@ -779,7 +602,7 @@ gitlink-cli/
|
|||
|
||||
## 文档
|
||||
|
||||
- [Skills 使用指南](./skills/README.md) — AI Agent Skills 详细说明
|
||||
- [Skills 使用指南](skills/README.md) — AI Agent Skills 详细说明
|
||||
- [设计文档](doc/design.md) — 架构设计和开发计划
|
||||
|
||||
## 常见问题
|
||||
|
|
@ -842,7 +665,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)
|
||||
|
||||
## 许可证
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,13 @@
|
|||
# Wiki Shortcut
|
||||
|
||||
新增 `wiki` Shortcut 组,支持 Wiki 页面管理:
|
||||
Added a new `wiki` shortcut group for repository wiki management.
|
||||
|
||||
- `wiki +list` - 列出 Wiki 页面(目录结构)
|
||||
- `wiki +view` - 按页面名称查看 Wiki 页面详情
|
||||
- `wiki +create` - 创建新的 Wiki 页面
|
||||
- `wiki +update` - 更新 Wiki 页面标题和/或内容
|
||||
- `wiki +delete` - 删除 Wiki 页面
|
||||
Included commands:
|
||||
|
||||
## 实现要点
|
||||
|
||||
- **API 端点**:基于 `/api/wiki/open/{action}` 扁平路径结构,覆盖 5 个 Wiki 管理接口:
|
||||
- `GET /api/wiki/open/wikiPages` — 目录列表
|
||||
- `GET /api/wiki/open/getWiki` — 查看页面
|
||||
- `POST /api/wiki/open/createWiki` — 创建页面
|
||||
- `PUT /api/wiki/open/updateWiki` — 更新页面
|
||||
- `DELETE /api/wiki/open/deleteWiki` — 删除页面
|
||||
- **标识方式**:Wiki 页面通过 `pageName`(slug)标识,所有操作需要 `projectId`(GitLink 项目数字 ID)
|
||||
- **内容编码**:创建和更新时,内容自动进行 base64 编码后以 `content_base64` 字段发送
|
||||
- **更新保护**:`+update` 要求必须提供 `--title` 和 `--page-name`;`--content` 为可选
|
||||
- **Shortcut 模式**:使用 `common.Shortcut` + `RuntimeContext` 框架,与其他模块保持一致
|
||||
- `wiki +list`
|
||||
- `wiki +view`
|
||||
- `wiki +create`
|
||||
- `wiki +update`
|
||||
- `wiki +delete`
|
||||
|
||||
The shortcut auto-resolves GitLink `projectId` from repository metadata when `--project-id` is omitted, supports both inline content and file-backed Markdown content, base64-encodes wiki content for the API, and validates conflicting or incomplete input before sending requests.
|
||||
|
|
|
|||
|
|
@ -21,17 +21,6 @@
|
|||
"cmd.config.list.short": "List all configuration values",
|
||||
"cmd.config.set.short": "Set a configuration value",
|
||||
"cmd.config.short": "Manage gitlink-cli configuration",
|
||||
"cmd.dataset.create.long": "Create the dataset of a repository with a title, description, optional license and research paper content.",
|
||||
"cmd.dataset.create.short": "Create a repository's dataset",
|
||||
"cmd.dataset.delete_attachment.long": "Delete a dataset attachment by its UUID. This is destructive: preview with --dry-run, then pass --yes to confirm.",
|
||||
"cmd.dataset.delete_attachment.short": "Delete a dataset attachment by UUID",
|
||||
"cmd.dataset.list.long": "List datasets for one or more GitLink projects by their numeric project IDs.",
|
||||
"cmd.dataset.list.short": "List datasets by project IDs",
|
||||
"cmd.dataset.short": "Dataset operations",
|
||||
"cmd.dataset.update.long": "Update the dataset of a repository (title, description, optional license and research paper content).",
|
||||
"cmd.dataset.update.short": "Update a repository's dataset",
|
||||
"cmd.dataset.view.long": "View a repository's dataset and its attachments. Use --page/--limit to paginate attachments.",
|
||||
"cmd.dataset.view.short": "View a repository's dataset",
|
||||
"cmd.doctor.long": "Run local diagnostics for gitlink-cli configuration, authentication, repository context and API connectivity.",
|
||||
"cmd.doctor.short": "Diagnose gitlink-cli environment problems",
|
||||
"cmd.issue.batch_close.long": "Close filtered issues in bulk.\n\nThis command defaults to dry-run mode and only prints matching issues.\nPass --yes to execute remote close operations. Use restrictive filters and a small limit.\n\nExamples:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
|
||||
|
|
@ -42,7 +31,6 @@
|
|||
"cmd.issue.batch_list.short": "List issue batch maintenance candidates without changing remote data",
|
||||
"cmd.issue.close.short": "Close an issue",
|
||||
"cmd.issue.comment.short": "Add a comment to an issue",
|
||||
"cmd.issue.comments.short": "List comments on an issue",
|
||||
"cmd.issue.create.short": "Create a new issue",
|
||||
"cmd.issue.list.short": "List issues",
|
||||
"cmd.issue.short": "Issue operations",
|
||||
|
|
@ -52,12 +40,9 @@
|
|||
"cmd.org.info.short": "Show organization details",
|
||||
"cmd.org.list.short": "List organizations",
|
||||
"cmd.org.members.short": "List organization members",
|
||||
"cmd.org.repos.short": "List repositories of an organization",
|
||||
"cmd.org.short": "Organization operations",
|
||||
"cmd.pr.close.short": "Close a pull request",
|
||||
"cmd.pr.comment.short": "Add a comment to a pull request",
|
||||
"cmd.pr.comments.short": "List comments on a pull request",
|
||||
"cmd.pr.commits.short": "List commits on a pull request",
|
||||
"cmd.pr.create.short": "Create a pull request",
|
||||
"cmd.pr.diff.short": "Show diff for a pull request",
|
||||
"cmd.pr.files.short": "List changed files in a pull request",
|
||||
|
|
@ -69,17 +54,6 @@
|
|||
"cmd.pr.version_diff.short": "Show diff for a pull request patchset version",
|
||||
"cmd.pr.versions.short": "List pull request patchset versions",
|
||||
"cmd.pr.view.short": "View pull request details",
|
||||
"cmd.profile.ability.long": "Show a user's development ability scores (influence, contribution, activity, experience, language) and language breakdown, sourced from the GitLink platform statistics API.",
|
||||
"cmd.profile.ability.short": "Show a user's development ability scores",
|
||||
"cmd.profile.activity.long": "Show a user's recent activity statistics: daily issues, pull requests, and commits over a time window.",
|
||||
"cmd.profile.activity.short": "Show a user's recent activity statistics",
|
||||
"cmd.profile.contribution.long": "Show a user's contribution heatmap (daily contribution counts) for a given year.",
|
||||
"cmd.profile.contribution.short": "Show a user's contribution heatmap",
|
||||
"cmd.profile.major.long": "Show a user's major/discipline categories (e.g. deep learning, quantum computing) inferred from their projects.",
|
||||
"cmd.profile.major.short": "Show a user's major/discipline categories",
|
||||
"cmd.profile.role.long": "Show a user's role positioning derived from the GitLink platform statistics API.",
|
||||
"cmd.profile.role.short": "Show a user's role positioning",
|
||||
"cmd.profile.short": "User profile and statistics operations",
|
||||
"cmd.release.create.short": "Create a release",
|
||||
"cmd.release.delete.short": "Delete a release",
|
||||
"cmd.release.list.short": "List releases",
|
||||
|
|
@ -109,16 +83,19 @@
|
|||
"cmd.webhook.test.short": "Trigger a test delivery for a webhook",
|
||||
"cmd.webhook.update.short": "Update a repository webhook while preserving unspecified fields when available",
|
||||
"cmd.webhook.view.short": "View webhook details",
|
||||
"cmd.wiki.create.short": "Create a wiki page",
|
||||
"cmd.wiki.delete.short": "Delete a wiki page",
|
||||
"cmd.wiki.list.short": "List wiki pages",
|
||||
"cmd.wiki.short": "Wiki operations",
|
||||
"cmd.wiki.update.short": "Update a wiki page",
|
||||
"cmd.wiki.view.short": "View a wiki page",
|
||||
"error.auth.delete_token_failed": "failed to delete token: {message}",
|
||||
"error.auth.login_failed": "login failed: {message}",
|
||||
"error.auth.store_token_failed": "failed to store token: {message}",
|
||||
"error.auth.token_empty": "token cannot be empty",
|
||||
"error.config.save_failed": "failed to save config: {message}",
|
||||
"error.dataset.delete_confirm": "dataset attachment deletion is destructive; run --dry-run first, then pass --yes to confirm",
|
||||
"error.missing_required_flag": "required flag --{name} is missing",
|
||||
"error.profile.user_required": "could not determine target user; pass --user or run gitlink-cli auth login",
|
||||
"error.unsupported_language": "unsupported language: {lang}",
|
||||
"flag.all": "Fetch all pages automatically (ignores --page)",
|
||||
"flag.api.batch_continue_on_error": "Continue running remaining batch requests after a failure",
|
||||
"flag.api.batch_dry_run": "Preview batch requests without sending remote requests",
|
||||
"flag.api.batch_file": "Read an API batch plan from a JSON file",
|
||||
|
|
@ -127,7 +104,6 @@
|
|||
"flag.api.body_file": "Read request body JSON from a file",
|
||||
"flag.api.body_stdin": "Read request body JSON from stdin",
|
||||
"flag.api.header": "Additional headers (key:value)",
|
||||
"flag.api.paginate": "Fetch all pages of a GET list endpoint and merge the results",
|
||||
"flag.api.query": "Query parameters (key=val&key2=val2)",
|
||||
"flag.auth.token": "Login by pasting an existing token",
|
||||
"flag.branch.from": "Source branch or commit",
|
||||
|
|
@ -136,17 +112,6 @@
|
|||
"flag.ci.stage": "Stage number",
|
||||
"flag.ci.step": "Step number",
|
||||
"flag.comment.body": "Comment body",
|
||||
"flag.dataset.description": "Dataset description",
|
||||
"flag.dataset.dry_run": "Preview the request without writing the dataset",
|
||||
"flag.dataset.dry_run_delete": "Preview the request without deleting the attachment",
|
||||
"flag.dataset.ids": "Comma-separated project IDs to query datasets for",
|
||||
"flag.dataset.license_id": "License ID",
|
||||
"flag.dataset.limit": "Attachment page size",
|
||||
"flag.dataset.page": "Attachment page number",
|
||||
"flag.dataset.paper_content": "Research paper content",
|
||||
"flag.dataset.title": "Dataset title",
|
||||
"flag.dataset.uuid": "Attachment UUID",
|
||||
"flag.dataset.yes": "Confirm the destructive deletion",
|
||||
"flag.debug": "Enable debug output",
|
||||
"flag.description": "Description",
|
||||
"flag.doctor.skip_network": "Skip authenticated API connectivity checks",
|
||||
|
|
@ -202,10 +167,6 @@
|
|||
"flag.pr.tag_id": "Issue tag ID",
|
||||
"flag.pr.title": "PR title",
|
||||
"flag.pr.version_id": "Patchset version ID",
|
||||
"flag.profile.end_time": "End time (Unix timestamp)",
|
||||
"flag.profile.start_time": "Start time (Unix timestamp)",
|
||||
"flag.profile.user": "Target user login (defaults to the authenticated user)",
|
||||
"flag.profile.year": "Year for the contribution heatmap (e.g. 2025)",
|
||||
"flag.release.body": "Release notes",
|
||||
"flag.release.id": "Release ID",
|
||||
"flag.release.id_or_tag": "Release ID or tag",
|
||||
|
|
@ -235,6 +196,12 @@
|
|||
"flag.webhook.secret_update": "Webhook secret. Pass it again if the server does not return existing secrets.",
|
||||
"flag.webhook.type": "Webhook type: gitea/slack/discord/dingtalk/telegram/msteams/feishu/matrix/jianmu/softbot",
|
||||
"flag.webhook.url": "Webhook target URL",
|
||||
"flag.wiki.content": "Wiki page content",
|
||||
"flag.wiki.file": "Read wiki page content from file",
|
||||
"flag.wiki.message": "Commit message",
|
||||
"flag.wiki.page": "Wiki page name",
|
||||
"flag.wiki.project_id": "GitLink project ID (auto-resolved when omitted)",
|
||||
"flag.wiki.title": "Wiki page title (defaults to --page)",
|
||||
"output.auth.env_hint": " Or set {env} environment variable",
|
||||
"output.auth.login_hint": " Run: gitlink-cli auth login",
|
||||
"output.config.file": "Config file: {path}",
|
||||
|
|
|
|||
|
|
@ -21,17 +21,6 @@
|
|||
"cmd.config.list.short": "列出所有配置项",
|
||||
"cmd.config.set.short": "设置配置项",
|
||||
"cmd.config.short": "管理 gitlink-cli 配置",
|
||||
"cmd.dataset.create.long": "为仓库创建数据集,包含标题、描述、可选许可证和研究论文内容。",
|
||||
"cmd.dataset.create.short": "创建仓库数据集",
|
||||
"cmd.dataset.delete_attachment.long": "按 UUID 删除数据集附件。该操作具有破坏性:先用 --dry-run 预览,再传 --yes 确认。",
|
||||
"cmd.dataset.delete_attachment.short": "按 UUID 删除数据集附件",
|
||||
"cmd.dataset.list.long": "按数字项目 ID 列出一个或多个 GitLink 项目的数据集。",
|
||||
"cmd.dataset.list.short": "按项目 ID 列出数据集",
|
||||
"cmd.dataset.short": "数据集操作",
|
||||
"cmd.dataset.update.long": "更新仓库数据集(标题、描述、可选许可证和研究论文内容)。",
|
||||
"cmd.dataset.update.short": "更新仓库数据集",
|
||||
"cmd.dataset.view.long": "查看仓库的数据集及其附件。用 --page/--limit 对附件分页。",
|
||||
"cmd.dataset.view.short": "查看仓库数据集",
|
||||
"cmd.doctor.long": "诊断 gitlink-cli 的配置、认证、仓库上下文和 API 连通性问题。",
|
||||
"cmd.doctor.short": "诊断 gitlink-cli 环境问题",
|
||||
"cmd.issue.batch_close.long": "批量关闭筛选后的议题。\n\n该命令默认处于 dry-run 模式,只打印匹配的议题。\n传入 --yes 后执行远端关闭操作。请使用严格筛选条件和较小 limit。\n\n示例:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
|
||||
|
|
@ -42,7 +31,6 @@
|
|||
"cmd.issue.batch_list.short": "列出议题批量维护候选项,不修改远端数据",
|
||||
"cmd.issue.close.short": "关闭议题",
|
||||
"cmd.issue.comment.short": "给议题添加评论",
|
||||
"cmd.issue.comments.short": "列出 Issue 的评论",
|
||||
"cmd.issue.create.short": "创建新议题",
|
||||
"cmd.issue.list.short": "列出议题",
|
||||
"cmd.issue.short": "议题操作",
|
||||
|
|
@ -52,12 +40,9 @@
|
|||
"cmd.org.info.short": "显示组织详情",
|
||||
"cmd.org.list.short": "列出组织",
|
||||
"cmd.org.members.short": "列出组织成员",
|
||||
"cmd.org.repos.short": "列出组织下的仓库",
|
||||
"cmd.org.short": "组织操作",
|
||||
"cmd.pr.close.short": "关闭拉取请求",
|
||||
"cmd.pr.comment.short": "给拉取请求添加评论",
|
||||
"cmd.pr.comments.short": "列出 PR 的评论",
|
||||
"cmd.pr.commits.short": "列出 PR 的提交",
|
||||
"cmd.pr.create.short": "创建拉取请求",
|
||||
"cmd.pr.diff.short": "显示拉取请求 diff",
|
||||
"cmd.pr.files.short": "列出拉取请求中的变更文件",
|
||||
|
|
@ -69,17 +54,6 @@
|
|||
"cmd.pr.version_diff.short": "显示拉取请求补丁集版本 diff",
|
||||
"cmd.pr.versions.short": "列出拉取请求补丁集版本",
|
||||
"cmd.pr.view.short": "查看拉取请求详情",
|
||||
"cmd.profile.ability.long": "显示用户的开发能力评分(影响力、贡献度、活跃度、项目经验、语言能力)及语言分布,数据来自 GitLink 平台统计接口。",
|
||||
"cmd.profile.ability.short": "显示用户开发能力评分",
|
||||
"cmd.profile.activity.long": "显示用户近期活动统计:时间范围内每日的疑修、合并请求和提交数量。",
|
||||
"cmd.profile.activity.short": "显示用户近期活动统计",
|
||||
"cmd.profile.contribution.long": "显示用户在指定年份的贡献热力图(每日贡献次数)。",
|
||||
"cmd.profile.contribution.short": "显示用户贡献热力图",
|
||||
"cmd.profile.major.long": "显示根据用户项目推断的专业/学科领域(如深度学习、量子计算)。",
|
||||
"cmd.profile.major.short": "显示用户专业/学科定位",
|
||||
"cmd.profile.role.long": "显示来自 GitLink 平台统计接口的用户角色定位。",
|
||||
"cmd.profile.role.short": "显示用户角色定位",
|
||||
"cmd.profile.short": "用户画像与统计操作",
|
||||
"cmd.release.create.short": "创建发布",
|
||||
"cmd.release.delete.short": "删除发布",
|
||||
"cmd.release.list.short": "列出发布",
|
||||
|
|
@ -109,16 +83,19 @@
|
|||
"cmd.webhook.test.short": "触发 Webhook 测试投递",
|
||||
"cmd.webhook.update.short": "更新仓库 Webhook,并在可用时保留未指定字段",
|
||||
"cmd.webhook.view.short": "查看 Webhook 详情",
|
||||
"cmd.wiki.create.short": "创建 Wiki 页面",
|
||||
"cmd.wiki.delete.short": "删除 Wiki 页面",
|
||||
"cmd.wiki.list.short": "列出 Wiki 页面",
|
||||
"cmd.wiki.short": "Wiki 操作",
|
||||
"cmd.wiki.update.short": "更新 Wiki 页面",
|
||||
"cmd.wiki.view.short": "查看 Wiki 页面",
|
||||
"error.auth.delete_token_failed": "删除 Token 失败:{message}",
|
||||
"error.auth.login_failed": "登录失败:{message}",
|
||||
"error.auth.store_token_failed": "保存 Token 失败:{message}",
|
||||
"error.auth.token_empty": "Token 不能为空",
|
||||
"error.config.save_failed": "保存配置失败:{message}",
|
||||
"error.dataset.delete_confirm": "删除数据集附件具有破坏性;请先 --dry-run 预览,再传 --yes 确认",
|
||||
"error.missing_required_flag": "缺少必需参数 --{name}",
|
||||
"error.profile.user_required": "无法确定目标用户;请通过 --user 指定,或先运行 gitlink-cli auth login 登录",
|
||||
"error.unsupported_language": "不支持的语言:{lang}",
|
||||
"flag.all": "自动获取全部分页(忽略 --page)",
|
||||
"flag.api.batch_continue_on_error": "批处理请求失败后继续执行后续请求",
|
||||
"flag.api.batch_dry_run": "预览批处理请求,不发送远端请求",
|
||||
"flag.api.batch_file": "从 JSON 文件读取 API 批处理计划",
|
||||
|
|
@ -127,7 +104,6 @@
|
|||
"flag.api.body_file": "从文件读取 JSON 请求体",
|
||||
"flag.api.body_stdin": "从标准输入读取 JSON 请求体",
|
||||
"flag.api.header": "附加请求头(key:value)",
|
||||
"flag.api.paginate": "自动获取 GET 列表接口的全部分页并合并结果",
|
||||
"flag.api.query": "查询参数(key=val&key2=val2)",
|
||||
"flag.auth.token": "通过粘贴已有 Token 登录",
|
||||
"flag.branch.from": "源分支或 Commit",
|
||||
|
|
@ -136,17 +112,6 @@
|
|||
"flag.ci.stage": "阶段编号",
|
||||
"flag.ci.step": "步骤编号",
|
||||
"flag.comment.body": "评论内容",
|
||||
"flag.dataset.description": "数据集描述",
|
||||
"flag.dataset.dry_run": "预览请求,不写入数据集",
|
||||
"flag.dataset.dry_run_delete": "预览请求,不删除附件",
|
||||
"flag.dataset.ids": "用于查询数据集的项目 ID,逗号分隔",
|
||||
"flag.dataset.license_id": "许可证 ID",
|
||||
"flag.dataset.limit": "附件每页数量",
|
||||
"flag.dataset.page": "附件页码",
|
||||
"flag.dataset.paper_content": "研究论文内容",
|
||||
"flag.dataset.title": "数据集标题",
|
||||
"flag.dataset.uuid": "附件 UUID",
|
||||
"flag.dataset.yes": "确认执行破坏性删除",
|
||||
"flag.debug": "启用调试输出",
|
||||
"flag.description": "描述",
|
||||
"flag.doctor.skip_network": "跳过需要访问 GitLink 的认证连通性检查",
|
||||
|
|
@ -202,10 +167,6 @@
|
|||
"flag.pr.tag_id": "议题标签 ID",
|
||||
"flag.pr.title": "PR 标题",
|
||||
"flag.pr.version_id": "补丁集版本 ID",
|
||||
"flag.profile.end_time": "结束时间(Unix 时间戳)",
|
||||
"flag.profile.start_time": "开始时间(Unix 时间戳)",
|
||||
"flag.profile.user": "目标用户登录名(默认为当前认证用户)",
|
||||
"flag.profile.year": "贡献热力图的年份(如 2025)",
|
||||
"flag.release.body": "发布说明",
|
||||
"flag.release.id": "发布 ID",
|
||||
"flag.release.id_or_tag": "发布 ID 或标签",
|
||||
|
|
@ -235,6 +196,12 @@
|
|||
"flag.webhook.secret_update": "Webhook 密钥。如果服务端不返回已有密钥,请再次传入。",
|
||||
"flag.webhook.type": "Webhook 类型:gitea/slack/discord/dingtalk/telegram/msteams/feishu/matrix/jianmu/softbot",
|
||||
"flag.webhook.url": "Webhook 目标 URL",
|
||||
"flag.wiki.content": "Wiki 页面内容",
|
||||
"flag.wiki.file": "从文件读取 Wiki 页面内容",
|
||||
"flag.wiki.message": "提交说明",
|
||||
"flag.wiki.page": "Wiki 页面名称",
|
||||
"flag.wiki.project_id": "GitLink 项目 ID(未传入时自动解析)",
|
||||
"flag.wiki.title": "Wiki 页面标题(默认使用 --page)",
|
||||
"output.auth.env_hint": " 或设置 {env} 环境变量",
|
||||
"output.auth.login_hint": " 运行:gitlink-cli auth login",
|
||||
"output.config.file": "配置文件:{path}",
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
"webhook": webhook.Shortcuts(tr),
|
||||
"wiki": wiki.Shortcuts(),
|
||||
"workflow": workflow.Shortcuts(),
|
||||
"wiki": wiki.Shortcuts(tr),
|
||||
}
|
||||
|
||||
descriptions := map[string]string{
|
||||
|
|
@ -77,6 +78,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
"webhook": tr.T("cmd.webhook.short"),
|
||||
"wiki": "Wiki page operations",
|
||||
"workflow": "AI agent workflow analysis",
|
||||
"wiki": tr.T("cmd.wiki.short"),
|
||||
}
|
||||
|
||||
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",
|
||||
"org", "user", "search", "tag", "commit", "ci", "workflow",
|
||||
"repo", "issue", "label", "license", "pr", "release", "branch",
|
||||
"org", "user", "search", "ci", "workflow",
|
||||
"compare", "member", "milestone", "pipeline", "webhook",
|
||||
"dataset", "health", "ignore", "wiki", "feishu",
|
||||
"health", "wiki",
|
||||
}
|
||||
|
||||
groupSet := map[string]bool{}
|
||||
|
|
|
|||
|
|
@ -1,129 +1,297 @@
|
|||
package wiki
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/output"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// Shortcuts returns wiki management shortcuts for GitLink.
|
||||
//
|
||||
// The wiki domain provides commands for listing, viewing, creating,
|
||||
// updating, and deleting wiki pages within a repository.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
const (
|
||||
wikiListPath = "/api/wiki/wikiPages"
|
||||
wikiViewPath = "/api/wiki/getWiki"
|
||||
wikiCreatePath = "/api/wiki/createWiki"
|
||||
wikiUpdatePath = "/api/wiki/updateWiki"
|
||||
wikiDeletePath = "/api/wiki/deleteWiki"
|
||||
)
|
||||
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "pages",
|
||||
Description: "列出 Wiki 页面",
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", "/api/wiki/wikiPages", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
Name: "list",
|
||||
Description: tr.T("cmd.wiki.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id")},
|
||||
},
|
||||
Run: runListWikiPages,
|
||||
},
|
||||
{
|
||||
Name: "get",
|
||||
Description: "获取 Wiki 页面内容",
|
||||
Name: "view",
|
||||
Description: tr.T("cmd.wiki.view.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Wiki 页面 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("/api/wiki/getWiki?id=%s", id)
|
||||
env, err := ctx.CallAPI("GET", path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.wiki.page"), Required: true},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id")},
|
||||
},
|
||||
Run: runViewWikiPage,
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Description: "创建 Wiki 页面",
|
||||
Description: tr.T("cmd.wiki.create.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "title", Short: "t", Usage: "页面标题", Required: true},
|
||||
{Name: "content", Short: "c", Usage: "页面内容(Markdown)", Required: true},
|
||||
{Name: "project", Usage: "项目 ID"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, err := ctx.RequireArg("title")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := ctx.RequireArg("content")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"title": title,
|
||||
"content": content,
|
||||
}
|
||||
if project := ctx.Arg("project"); project != "" {
|
||||
body["project_id"] = project
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", "/api/wiki/createWiki", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.wiki.page"), Required: true},
|
||||
{Name: "title", Short: "t", Usage: tr.T("flag.wiki.title")},
|
||||
{Name: "content", Short: "c", Usage: tr.T("flag.wiki.content")},
|
||||
{Name: "file", Short: "f", Usage: tr.T("flag.wiki.file")},
|
||||
{Name: "message", Short: "m", Usage: tr.T("flag.wiki.message")},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id")},
|
||||
},
|
||||
Run: runCreateWikiPage,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Description: "更新 Wiki 页面",
|
||||
Description: tr.T("cmd.wiki.update.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Wiki 页面 ID", Required: true},
|
||||
{Name: "title", Short: "t", Usage: "新标题"},
|
||||
{Name: "content", Short: "c", Usage: "新内容(Markdown)"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := map[string]interface{}{"id": id}
|
||||
if t := ctx.Arg("title"); t != "" {
|
||||
body["title"] = t
|
||||
}
|
||||
if c := ctx.Arg("content"); c != "" {
|
||||
body["content"] = c
|
||||
}
|
||||
env, err := ctx.CallAPI("PUT", "/api/wiki/updateWiki", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.wiki.page"), Required: true},
|
||||
{Name: "title", Short: "t", Usage: tr.T("flag.wiki.title")},
|
||||
{Name: "content", Short: "c", Usage: tr.T("flag.wiki.content")},
|
||||
{Name: "file", Short: "f", Usage: tr.T("flag.wiki.file")},
|
||||
{Name: "message", Short: "m", Usage: tr.T("flag.wiki.message")},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id")},
|
||||
},
|
||||
Run: runUpdateWikiPage,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Description: "删除 Wiki 页面",
|
||||
Description: tr.T("cmd.wiki.delete.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Wiki 页面 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := map[string]interface{}{"id": id}
|
||||
env, err := ctx.CallAPI("POST", "/api/wiki/deleteWiki", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.wiki.page"), Required: true},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id")},
|
||||
},
|
||||
Run: runDeleteWikiPage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
||||
func runListWikiPages(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
query, err := buildWikiQuery(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := callWikiAPIWithQuery(ctx, "GET", wikiListPath, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runViewWikiPage(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
page, err := ctx.RequireArg("page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query, err := buildWikiQuery(ctx, page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := callWikiAPIWithQuery(ctx, "GET", wikiViewPath, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runCreateWikiPage(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := buildWikiWritePayload(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := callWikiAPI(ctx, "POST", wikiCreatePath, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runUpdateWikiPage(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := buildWikiWritePayload(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := callWikiAPI(ctx, "PUT", wikiUpdatePath, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runDeleteWikiPage(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
page, err := ctx.RequireArg("page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := resolveProjectID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": projectID,
|
||||
"pageName": page,
|
||||
}
|
||||
env, err := callWikiAPI(ctx, "DELETE", wikiDeletePath, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func buildWikiQuery(ctx *common.RuntimeContext, page string) (url.Values, error) {
|
||||
projectID, err := resolveProjectID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := url.Values{}
|
||||
query.Set("owner", ctx.Owner)
|
||||
query.Set("repo", ctx.Repo)
|
||||
query.Set("projectId", strconv.Itoa(projectID))
|
||||
if page != "" {
|
||||
query.Set("pageName", page)
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func callWikiAPI(ctx *common.RuntimeContext, method, path string, body interface{}) (*output.Envelope, error) {
|
||||
env, err := ctx.CallAPI(method, path, body)
|
||||
if err != nil {
|
||||
return nil, wrapWikiAPIError(path, err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func callWikiAPIWithQuery(ctx *common.RuntimeContext, method, path string, query url.Values) (*output.Envelope, error) {
|
||||
env, err := ctx.CallAPIWithQuery(method, path, query)
|
||||
if err != nil {
|
||||
return nil, wrapWikiAPIError(path, err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func wrapWikiAPIError(path string, err error) error {
|
||||
var apiErr *client.APIError
|
||||
if errors.As(err, &apiErr) && apiErr.StatusCode == 404 {
|
||||
return fmt.Errorf("GitLink Wiki API %s returned 404; confirm the repository has Wiki enabled and the Wiki OpenAPI is available: %w", path, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func buildWikiWritePayload(ctx *common.RuntimeContext, requireContent bool) (map[string]interface{}, error) {
|
||||
page, err := ctx.RequireArg("page")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectID, err := resolveProjectID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
title := ctx.Arg("title")
|
||||
if title == "" {
|
||||
title = page
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": projectID,
|
||||
"pageName": page,
|
||||
"title": title,
|
||||
}
|
||||
if message := ctx.Arg("message"); message != "" {
|
||||
payload["message"] = message
|
||||
}
|
||||
contentBase64, ok, err := readWikiContent(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
payload["content_base64"] = contentBase64
|
||||
} else if requireContent {
|
||||
return nil, fmt.Errorf("one of --content or --file is required")
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func readWikiContent(ctx *common.RuntimeContext) (string, bool, error) {
|
||||
content := ctx.Arg("content")
|
||||
filePath := ctx.Arg("file")
|
||||
if content != "" && filePath != "" {
|
||||
return "", false, fmt.Errorf("use only one of --content or --file")
|
||||
}
|
||||
if filePath != "" {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("read wiki file: %w", err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(data), true, nil
|
||||
}
|
||||
if content != "" {
|
||||
return base64.StdEncoding.EncodeToString([]byte(content)), true, nil
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
func resolveProjectID(ctx *common.RuntimeContext) (int, error) {
|
||||
if value := strings.TrimSpace(ctx.Arg("project-id")); value != "" {
|
||||
projectID, err := strconv.Atoi(value)
|
||||
if err != nil || projectID <= 0 {
|
||||
return 0, fmt.Errorf("invalid --project-id %q", value)
|
||||
}
|
||||
return projectID, nil
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("GET", ctx.RepoPath(), nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("resolve project ID: %w", err)
|
||||
}
|
||||
data, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unexpected repository response format")
|
||||
}
|
||||
for _, key := range []string{"project_id", "id"} {
|
||||
if value, ok := data[key].(float64); ok && value > 0 {
|
||||
return int(value), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("repository response missing project_id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
package wiki
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -11,253 +15,216 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func TestWikiPages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockStatus int
|
||||
mockBody string
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{"正常返回", 200, `{"wikiPages": []}`, false, ""},
|
||||
{"API 404", 404, `{"error": "not found"}`, true, "404"},
|
||||
{"返回 HTML", 200, `<!DOCTYPE html><html><body>Login</body></html>`, true, "HTML"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
w.WriteHeader(tt.mockStatus)
|
||||
w.Write([]byte(tt.mockBody))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "pages")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{},
|
||||
}
|
||||
err := shortcut.Run(ctx)
|
||||
|
||||
if tt.wantErr && err == nil {
|
||||
t.Fatal("期望错误但为 nil")
|
||||
}
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Fatalf("不期望错误: %v", err)
|
||||
}
|
||||
if tt.wantErr && tt.errContains != "" && err != nil {
|
||||
if !strings.Contains(err.Error(), tt.errContains) {
|
||||
t.Errorf("错误应包含 %q: %s", tt.errContains, err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args map[string]string
|
||||
mockStatus int
|
||||
mockBody string
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{"正常获取", map[string]string{"id": "42"}, 200, `{"id": 42, "title": "Home"}`, false, ""},
|
||||
{"缺少 id", map[string]string{}, 200, `{}`, true, ""},
|
||||
{"API 404", map[string]string{"id": "999"}, 404, `{"error": "not found"}`, true, "404"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.URL.Path, "/api/wiki/getWiki") {
|
||||
t.Errorf("expected path containing /api/wiki/getWiki, got %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(tt.mockStatus)
|
||||
w.Write([]byte(tt.mockBody))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "get")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: tt.args,
|
||||
}
|
||||
err := shortcut.Run(ctx)
|
||||
|
||||
if tt.wantErr && err == nil {
|
||||
t.Fatal("期望错误但为 nil")
|
||||
}
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Fatalf("不期望错误: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiCreate(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
func TestWikiListAutoResolvesProjectID(t *testing.T) {
|
||||
called := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/owner/repo.json":
|
||||
writeJSON(t, w, map[string]interface{}{"project_id": float64(1546652)})
|
||||
case r.Method == "GET" && r.URL.Path == "/api/wiki/wikiPages.json":
|
||||
called = true
|
||||
query := r.URL.Query()
|
||||
assertEqual(t, query.Get("owner"), "owner")
|
||||
assertEqual(t, query.Get("repo"), "repo")
|
||||
assertEqual(t, query.Get("projectId"), "1546652")
|
||||
writeJSON(t, w, map[string]interface{}{"message": "success", "data": []interface{}{}})
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
if !strings.Contains(r.URL.Path, "/api/wiki/createWiki") {
|
||||
t.Errorf("expected path containing /api/wiki/createWiki, got %s", r.URL.Path)
|
||||
}
|
||||
payload = decodeWikiJSON(t, r)
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"status": 0, "message": "success"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "create")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{
|
||||
"title": "Getting Started",
|
||||
"content": "# Hello\nWelcome to the wiki",
|
||||
},
|
||||
err := runWikiShortcut(t, server, "list", map[string]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("list shortcut failed: %v", err)
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
if !called {
|
||||
t.Fatal("wiki list endpoint was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiViewUsesExplicitProjectID(t *testing.T) {
|
||||
called := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/api/wiki/getWiki.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
called = true
|
||||
query := r.URL.Query()
|
||||
assertEqual(t, query.Get("owner"), "owner")
|
||||
assertEqual(t, query.Get("repo"), "repo")
|
||||
assertEqual(t, query.Get("projectId"), "42")
|
||||
assertEqual(t, query.Get("pageName"), "Home")
|
||||
writeJSON(t, w, map[string]interface{}{"message": "success", "data": map[string]interface{}{"title": "Home"}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runWikiShortcut(t, server, "view", map[string]string{
|
||||
"page": "Home",
|
||||
"project-id": "42",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("view shortcut failed: %v", err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("wiki view endpoint was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiCreateBuildsPayloadFromContent(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/owner/repo.json":
|
||||
writeJSON(t, w, map[string]interface{}{"project_id": float64(1546652)})
|
||||
case r.Method == "POST" && r.URL.Path == "/api/wiki/createWiki.json":
|
||||
payload = decodeJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"message": "201"})
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runWikiShortcut(t, server, "create", map[string]string{
|
||||
"page": "Home",
|
||||
"title": "Welcome",
|
||||
"content": "hello wiki",
|
||||
"message": "Add Home",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create shortcut failed: %v", err)
|
||||
}
|
||||
if payload["title"] != "Getting Started" {
|
||||
t.Errorf("expected title 'Getting Started', got %v", payload["title"])
|
||||
}
|
||||
if payload["content"] != "# Hello\nWelcome to the wiki" {
|
||||
t.Errorf("unexpected content: %v", payload["content"])
|
||||
}
|
||||
|
||||
assertEqual(t, payload["owner"], "owner")
|
||||
assertEqual(t, payload["repo"], "repo")
|
||||
assertEqual(t, payload["projectId"], float64(1546652))
|
||||
assertEqual(t, payload["pageName"], "Home")
|
||||
assertEqual(t, payload["title"], "Welcome")
|
||||
assertEqual(t, payload["message"], "Add Home")
|
||||
assertEqual(t, payload["content_base64"], base64.StdEncoding.EncodeToString([]byte("hello wiki")))
|
||||
}
|
||||
|
||||
func TestWikiCreateWithProject(t *testing.T) {
|
||||
func TestWikiUpdateBuildsPayloadFromFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
filePath := filepath.Join(tempDir, "wiki.md")
|
||||
if err := os.WriteFile(filePath, []byte("# Updated\n"), 0o644); err != nil {
|
||||
t.Fatalf("write wiki file: %v", err)
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
payload = decodeWikiJSON(t, r)
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"status": 0}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "create")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{
|
||||
"title": "Test",
|
||||
"content": "Body",
|
||||
"project": "123",
|
||||
},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
t.Fatalf("create shortcut failed: %v", err)
|
||||
}
|
||||
if payload["project_id"] != "123" {
|
||||
t.Errorf("expected project_id '123', got %v", payload["project_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiCreateMissingTitle(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("should not call API when --title is missing")
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "create")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"content": "only content"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err == nil {
|
||||
t.Fatal("expected error when --title is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiUpdate(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "PUT" {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
if r.Method != "PUT" || r.URL.Path != "/api/wiki/updateWiki.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
if !strings.Contains(r.URL.Path, "/api/wiki/updateWiki") {
|
||||
t.Errorf("expected path containing /api/wiki/updateWiki, got %s", r.URL.Path)
|
||||
}
|
||||
payload = decodeWikiJSON(t, r)
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"status": 0, "message": "success"}`))
|
||||
payload = decodeJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"message": "success"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "update")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{
|
||||
"id": "42",
|
||||
"title": "Updated Title",
|
||||
"content": "Updated content",
|
||||
},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
err := runWikiShortcut(t, server, "update", map[string]string{
|
||||
"page": "Home",
|
||||
"file": filePath,
|
||||
"project-id": "42",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("update shortcut failed: %v", err)
|
||||
}
|
||||
if payload["id"] != "42" {
|
||||
t.Errorf("expected id '42', got %v", payload["id"])
|
||||
}
|
||||
if payload["title"] != "Updated Title" {
|
||||
t.Errorf("expected title 'Updated Title', got %v", payload["title"])
|
||||
}
|
||||
|
||||
assertEqual(t, payload["projectId"], float64(42))
|
||||
assertEqual(t, payload["pageName"], "Home")
|
||||
assertEqual(t, payload["title"], "Home")
|
||||
assertEqual(t, payload["content_base64"], base64.StdEncoding.EncodeToString([]byte("# Updated\n")))
|
||||
}
|
||||
|
||||
func TestWikiDelete(t *testing.T) {
|
||||
func TestWikiDeleteSendsBody(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
if r.Method != "DELETE" || r.URL.Path != "/api/wiki/deleteWiki.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
if !strings.Contains(r.URL.Path, "/api/wiki/deleteWiki") {
|
||||
t.Errorf("expected path containing /api/wiki/deleteWiki, got %s", r.URL.Path)
|
||||
}
|
||||
payload = decodeWikiJSON(t, r)
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"status": 0, "message": "success"}`))
|
||||
payload = decodeJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"message": "success"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "delete")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"id": "42"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
err := runWikiShortcut(t, server, "delete", map[string]string{
|
||||
"page": "Home",
|
||||
"project-id": "42",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("delete shortcut failed: %v", err)
|
||||
}
|
||||
if payload["id"] != "42" {
|
||||
t.Errorf("expected id '42', got %v", payload["id"])
|
||||
|
||||
assertEqual(t, payload["owner"], "owner")
|
||||
assertEqual(t, payload["repo"], "repo")
|
||||
assertEqual(t, payload["projectId"], float64(42))
|
||||
assertEqual(t, payload["pageName"], "Home")
|
||||
}
|
||||
|
||||
func TestWikiOpenAPI404HasActionableError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/api/wiki/wikiPages.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"status": 404, "message": "not found"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runWikiShortcut(t, server, "list", map[string]string{
|
||||
"project-id": "42",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected wiki API 404 to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "GitLink Wiki API /api/wiki/wikiPages returned 404") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Wiki OpenAPI is available") {
|
||||
t.Fatalf("expected actionable wiki API hint, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiDeleteMissingId(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("should not call API when --id is missing")
|
||||
}))
|
||||
func TestWikiRejectsInvalidInput(t *testing.T) {
|
||||
server := httptest.NewServer(http.NotFoundHandler())
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findWikiShortcut(t, "delete")
|
||||
if err := runWikiShortcut(t, server, "view", map[string]string{
|
||||
"page": "Home",
|
||||
"project-id": "abc",
|
||||
}); err == nil {
|
||||
t.Fatal("expected invalid project-id to fail")
|
||||
}
|
||||
if err := runWikiShortcut(t, server, "create", map[string]string{
|
||||
"page": "Home",
|
||||
"project-id": "42",
|
||||
}); err == nil {
|
||||
t.Fatal("expected missing content to fail")
|
||||
}
|
||||
if err := runWikiShortcut(t, server, "create", map[string]string{
|
||||
"page": "Home",
|
||||
"project-id": "42",
|
||||
"content": "hello",
|
||||
"file": "wiki.md",
|
||||
}); err == nil {
|
||||
t.Fatal("expected content/file conflict to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func runWikiShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
shortcut := findWikiShortcut(t, name)
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err == nil {
|
||||
t.Fatal("expected error when --id is missing")
|
||||
Client: &client.Client{
|
||||
HTTP: server.Client(),
|
||||
BaseURL: server.URL,
|
||||
},
|
||||
Owner: "owner",
|
||||
Repo: "repo",
|
||||
Format: "json",
|
||||
Args: args,
|
||||
}
|
||||
return shortcut.Run(ctx)
|
||||
}
|
||||
|
||||
func findWikiShortcut(t *testing.T, name string) *common.Shortcut {
|
||||
|
|
@ -271,7 +238,7 @@ func findWikiShortcut(t *testing.T, name string) *common.Shortcut {
|
|||
return nil
|
||||
}
|
||||
|
||||
func decodeWikiJSON(t *testing.T, r *http.Request) map[string]interface{} {
|
||||
func decodeJSON(t *testing.T, r *http.Request) map[string]interface{} {
|
||||
t.Helper()
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
|
|
@ -279,3 +246,18 @@ func decodeWikiJSON(t *testing.T, r *http.Request) map[string]interface{} {
|
|||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func writeJSON(t *testing.T, w http.ResponseWriter, payload interface{}) {
|
||||
t.Helper()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
t.Fatalf("failed to write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, got interface{}, want interface{}) {
|
||||
t.Helper()
|
||||
if fmt.Sprintf("%v", got) != fmt.Sprintf("%v", want) {
|
||||
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,43 @@
|
|||
---
|
||||
name: gitlink-wiki
|
||||
version: 1.0.0
|
||||
description: "Wiki 操作:查看、创建、更新、删除 Wiki 页面。当用户需要管理 GitLink 仓库 Wiki 时触发。"
|
||||
description: "Wiki management: list, view, create, update, and delete repository wiki pages."
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli wiki --help"
|
||||
shortcuts:
|
||||
- "wiki +list"
|
||||
- "wiki +view"
|
||||
- "wiki +create"
|
||||
- "wiki +update"
|
||||
- "wiki +delete"
|
||||
---
|
||||
|
||||
# gitlink-wiki(Wiki 操作)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有 Shortcuts 在执行写入/删除操作前,务必先确认用户意图。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
|
||||
# gitlink-wiki
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Shortcut | 说明 | 需要认证 |
|
||||
|----------|------|----------|
|
||||
| `wiki +list` | 列出 Wiki 页面 | 是 |
|
||||
| `wiki +view` | 查看 Wiki 页面内容 | 是 |
|
||||
| `wiki +create` | 创建 Wiki 页面 | 是 |
|
||||
| `wiki +update` | 更新 Wiki 页面 | 是 |
|
||||
| `wiki +delete` | 删除 Wiki 页面 | 是 |
|
||||
| Shortcut | Description |
|
||||
|----------|-------------|
|
||||
| `wiki +list` | List repository wiki pages |
|
||||
| `wiki +view` | View a wiki page |
|
||||
| `wiki +create` | Create a wiki page |
|
||||
| `wiki +update` | Update a wiki page |
|
||||
| `wiki +delete` | Delete a wiki page |
|
||||
|
||||
## 使用示例
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# 列出所有 Wiki 页面
|
||||
gitlink-cli wiki +list --owner myuser --repo myrepo
|
||||
|
||||
# 查看 Wiki 页面
|
||||
gitlink-cli wiki +view --name Home
|
||||
|
||||
# 创建 Wiki 页面
|
||||
gitlink-cli wiki +create --name Guide --content "使用指南内容"
|
||||
|
||||
# 更新 Wiki 页面(带提交信息)
|
||||
gitlink-cli wiki +update --name Guide --content "更新后的内容" --message "更新使用指南"
|
||||
|
||||
# 删除 Wiki 页面
|
||||
gitlink-cli wiki +delete --name OldPage
|
||||
gitlink-cli wiki +list --owner Gitlink --repo forgeplus
|
||||
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --page Home
|
||||
gitlink-cli wiki +create --owner Gitlink --repo forgeplus \
|
||||
--page Home --title Home --content "Welcome to the project wiki"
|
||||
gitlink-cli wiki +update --owner Gitlink --repo forgeplus \
|
||||
--page Home --file docs/wiki-home.md --message "Update Home"
|
||||
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --page Home
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
## Notes
|
||||
|
||||
- Wiki 命令会自动从仓库信息中获取 `projectId`,无需手动指定
|
||||
- `--content` 参数的内容会自动进行 base64 编码
|
||||
- 在 git 仓库目录下执行时,`--owner` 和 `--repo` 会自动解析
|
||||
`--project-id` is optional. When omitted, the CLI reads repository metadata and uses the returned `project_id`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue