forked from Gitlink/gitlink-cli
Merge PR #316: feat(pm): add project management shortcuts
# Conflicts: # README.md # shortcuts/pm/pm.go # shortcuts/pm/pm_test.go # shortcuts/register_test.go # skills/README.md
This commit is contained in:
commit
9aa854b5de
210
README.md
210
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, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/README.md).
|
||||
|
||||
**[中文文档](./README.zh-CN.md)**
|
||||
|
||||
|
|
@ -78,11 +78,19 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
|||
<a href="https://www.gitlink.org.cn/jiangtx" title="jiangtx"><img src="https://www.gitlink.org.cn/system/lets/letter_avatars/2/J/67_157_94/120.png" width="40" height="40" alt="jiangtx" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/jiangtx">jiangtx</a></sub>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://www.gitlink.org.cn/luwanzhou" title="luwanzhou"><img src="https://www.gitlink.org.cn/system/lets/letter_avatars/2/L/165_135_246/120.png" width="40" height="40" alt="luwanzhou" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/luwanzhou">luwanzhou</a></sub>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://www.gitlink.org.cn/whale_hihihi" title="whale_hihihi"><img src="https://www.gitlink.org.cn/images/avatars/User/137722?t=1778575729" width="40" height="40" alt="whale_hihihi" style="border-radius: 50%;"></a>
|
||||
<br><sub><a href="https://www.gitlink.org.cn/whale_hihihi">whale_hihihi</a></sub>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Why gitlink-cli?
|
||||
|
||||
- **Agent-Native Design** — Structured [Skills](./skills/) out of the box, compatible with Claude Code, OpenClaw, and other AI platforms — Agents can operate GitLink with zero extra setup
|
||||
- **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
|
||||
- **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
|
||||
|
|
@ -96,7 +104,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 |
|
||||
| 🐛 Issue | Create, update, close, batch close, comment on issues |
|
||||
| 🐛 Issue | Create, update, close, batch close/update/delete, 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 |
|
||||
|
|
@ -106,8 +114,11 @@ 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, and delete wiki pages |
|
||||
| 🔍 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 |
|
||||
|
||||
|
|
@ -216,11 +227,6 @@ gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
|||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main
|
||||
|
||||
# Read raw files and common dependency manifests
|
||||
gitlink-cli repo +raw --owner Gitlink --repo forgeplus --path LICENSE --ref master
|
||||
gitlink-cli repo +file-exists --owner Gitlink --repo forgeplus --path package.json --ref master
|
||||
gitlink-cli repo +manifest --owner Gitlink --repo forgeplus --kind go --ref master
|
||||
|
||||
# Show language breakdown
|
||||
gitlink-cli repo +languages --owner Gitlink --repo forgeplus
|
||||
|
||||
|
|
@ -268,6 +274,28 @@ 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
|
||||
|
|
@ -290,31 +318,6 @@ gitlink-cli member +role --owner Gitlink --repo forgeplus --user-id 101 --role D
|
|||
gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role developer --apply true
|
||||
```
|
||||
|
||||
### Organization Operations
|
||||
|
||||
```bash
|
||||
# List organizations
|
||||
gitlink-cli org +list --page 1 --limit 20
|
||||
|
||||
# Show organization details
|
||||
gitlink-cli org +info --id Gitlink
|
||||
|
||||
# List organization members with team or keyword filters
|
||||
gitlink-cli org +members --id Gitlink --team Platform --format json
|
||||
gitlink-cli org +members --id Gitlink --keyword alice --limit 50 --format json
|
||||
|
||||
# List organization teams with normalized user details
|
||||
gitlink-cli org +teams --id Gitlink --authorize write --include-users --format json
|
||||
|
||||
# Preview and create an organization team
|
||||
gitlink-cli org +team-create --id Gitlink --name platform --nickname "Platform Team" --dry-run
|
||||
gitlink-cli org +team-create --id Gitlink --name platform --nickname "Platform Team"
|
||||
|
||||
# Preview and remove an organization member
|
||||
gitlink-cli org +member-remove --id Gitlink --login alice --dry-run
|
||||
gitlink-cli org +member-remove --id Gitlink --user-id 101
|
||||
```
|
||||
|
||||
### Issue Management
|
||||
|
||||
```bash
|
||||
|
|
@ -324,59 +327,34 @@ gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
|||
# Create an issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" -b "Steps to reproduce..."
|
||||
|
||||
# Create an issue from a Markdown file
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" --body-file issue.md
|
||||
|
||||
# Create an issue with metadata
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" --priority-id 3 --tag-ids 4,5 --assigner-ids 7
|
||||
|
||||
# View an issue
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus --number 123
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus -i 123
|
||||
|
||||
# Update issue metadata
|
||||
gitlink-cli issue +update --owner Gitlink --repo forgeplus --number 123 --priority-id 4 --branch bugfix/login --due-date 2026-06-15
|
||||
|
||||
# Update issue description from a file
|
||||
gitlink-cli issue +update --owner Gitlink --repo forgeplus --number 123 --body-file update.md
|
||||
|
||||
# Close an issue
|
||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus --number 123
|
||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
|
||||
|
||||
# Preview batch close without changing data
|
||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,124 --dry-run
|
||||
|
||||
# Preview a shared batch update
|
||||
gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --numbers 123,124 --state closed --priority-id 4 --dry-run
|
||||
|
||||
# Batch update issues from a CSV file
|
||||
gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --from issues.csv --assigner-ids 7 --due-date 2026-06-15
|
||||
|
||||
# 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 --number 123 -b "Fixed"
|
||||
|
||||
# List, update, delete, and inspect issue comments/journals
|
||||
gitlink-cli issue +comments --owner Gitlink --repo forgeplus --number 123 --category comment
|
||||
gitlink-cli issue +comment-update --owner Gitlink --repo forgeplus --number 123 --comment-id 58 -b "Updated comment" --dry-run
|
||||
gitlink-cli issue +comment-delete --owner Gitlink --repo forgeplus --number 123 --comment-id 58 --dry-run
|
||||
gitlink-cli issue +comment-children --owner Gitlink --repo forgeplus --number 123 --comment-id 58
|
||||
|
||||
# Add a comment from a file
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 --body-file comment.md
|
||||
|
||||
# Batch comment on issues
|
||||
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --numbers 123,124 --body-file comment.md --dry-run
|
||||
|
||||
# Reply to a comment and list a comment's replies
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Agreed" --reply-to 456
|
||||
gitlink-cli issue +comment-replies --owner Gitlink --repo forgeplus -n 123 -c 456
|
||||
|
||||
# List, edit, and delete issue comments
|
||||
gitlink-cli issue +comments --owner Gitlink --repo forgeplus -n 123 --category comment
|
||||
gitlink-cli issue +comment-edit --owner Gitlink --repo forgeplus -n 123 -c 456 -b "Updated"
|
||||
gitlink-cli issue +comment-delete --owner Gitlink --repo forgeplus -n 123 -c 456
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Fixed"
|
||||
|
||||
# List issue assigners
|
||||
gitlink-cli issue +assigners --owner Gitlink --repo forgeplus
|
||||
|
|
@ -454,12 +432,6 @@ gitlink-cli pr +reviews --owner Gitlink --repo forgeplus -i 42
|
|||
# Create a PR review (with dry-run preview)
|
||||
gitlink-cli pr +review --owner Gitlink --repo forgeplus -i 42 --status approved -c "LGTM" --dry-run
|
||||
gitlink-cli pr +review --owner Gitlink --repo forgeplus -i 42 --status approved -c "LGTM"
|
||||
|
||||
# List and manage PR review comments
|
||||
gitlink-cli pr +review-comments --owner Gitlink --repo forgeplus -i 42 --state opened
|
||||
gitlink-cli pr +review-comment --owner Gitlink --repo forgeplus -i 42 --review-id 10 --commit abc123 --line-code abc123_0_10 --path README.md --note "Please fix" --dry-run
|
||||
gitlink-cli pr +review-comment-update --owner Gitlink --repo forgeplus -i 42 --comment-id 200 --state resolved --dry-run
|
||||
gitlink-cli pr +review-comment-delete --owner Gitlink --repo forgeplus -i 42 --comment-id 200 --dry-run
|
||||
```
|
||||
|
||||
### Branch Management
|
||||
|
|
@ -536,6 +508,36 @@ 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
|
||||
|
|
@ -546,6 +548,29 @@ 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.
|
||||
|
||||
```bash
|
||||
# Development ability scores + language breakdown
|
||||
gitlink-cli profile +ability --user zhangsan
|
||||
|
||||
# Role positioning / major (discipline) categories
|
||||
gitlink-cli profile +role --user zhangsan
|
||||
gitlink-cli profile +major --user zhangsan
|
||||
|
||||
# Ability within a time window (Unix timestamps)
|
||||
gitlink-cli profile +ability --user zhangsan --start-time 1704067200 --end-time 1735689600
|
||||
|
||||
# Recent activity (issues / PRs / commits per day) for the current user
|
||||
gitlink-cli profile +activity
|
||||
|
||||
# Contribution heatmap for a given year
|
||||
gitlink-cli profile +contribution --user zhangsan --year 2025
|
||||
```
|
||||
|
||||
### Workflow Agent Commands
|
||||
|
||||
`workflow` provides rule-based repository analysis for maintainers and AI Agents. It currently supports:
|
||||
|
|
@ -645,6 +670,32 @@ Safety:
|
|||
- `workflow +pr-summary` does not comment, approve, reject, or merge pull requests.
|
||||
- `workflow +repo-report` aggregates health, issue triage, and PR review summary signals without remote writes.
|
||||
|
||||
### Dataset
|
||||
|
||||
`dataset` manages and queries GitLink research datasets (title, description,
|
||||
paper content, license, owning project).
|
||||
|
||||
```bash
|
||||
# List datasets for one or more projects (by numeric project ID)
|
||||
gitlink-cli dataset +list --ids 5988
|
||||
|
||||
# View a repository's dataset and attachments
|
||||
gitlink-cli dataset +view --owner Gitlink --repo forgeplus
|
||||
|
||||
# Create / update a repository's dataset (preview first with --dry-run)
|
||||
gitlink-cli dataset +create --owner me --repo proj -t "My dataset" -d "..." --license-id 359 --dry-run
|
||||
gitlink-cli dataset +update --owner me --repo proj -t "My dataset" -d "updated"
|
||||
|
||||
# Delete a dataset attachment (destructive: preview, then confirm with --yes)
|
||||
gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --dry-run
|
||||
gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --yes
|
||||
```
|
||||
|
||||
> Note: `dataset +list` (platform dataset query) is verified on production
|
||||
> gitlink.org.cn. The per-repo `+view`/`+create`/`+update` routes follow the
|
||||
> published OpenAPI contract but are not yet deployed on production (they return
|
||||
> 404 there); they will work once the platform enables them.
|
||||
|
||||
### Raw API
|
||||
|
||||
For endpoints not covered by shortcuts, use the Raw API directly:
|
||||
|
|
@ -701,13 +752,13 @@ 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, comment, etc.) |
|
||||
| `gitlink-issue` | Issue operations (create, update, close, batch update/delete, 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) |
|
||||
|
|
@ -748,6 +799,7 @@ gitlink-cli/
|
|||
│ ├── org/ # Organization shortcuts
|
||||
│ ├── ci/ # CI shortcuts
|
||||
│ ├── pipeline/ # Pipeline shortcuts
|
||||
│ ├── pm/ # Project management shortcuts
|
||||
│ ├── search/ # Search shortcuts
|
||||
│ ├── user/ # User shortcuts
|
||||
│ └── register.go # Registration entry point
|
||||
|
|
@ -771,7 +823,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
|
||||
|
|
@ -836,7 +888,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/REFERENCE.md](skills/gitlink-shared/REFERENCE.md).
|
||||
See [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md).
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -553,6 +553,25 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow
|
|||
gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run
|
||||
```
|
||||
|
||||
### 项目管理
|
||||
|
||||
`pm` 暴露 GitLink 项目管理数据,覆盖看板、Sprint、周报、标签、流水线和 Action 运行记录。
|
||||
PM 命令需要传入数字项目 ID。
|
||||
|
||||
```bash
|
||||
# 查询 PM 看板和 Sprint Issue
|
||||
gitlink-cli pm +dashboards --project-id 123 --limit 20
|
||||
gitlink-cli pm +sprint-issues --project-id 123 --page 1 --limit 20
|
||||
|
||||
# 查询周报 Issue 和 PM Issue 标签
|
||||
gitlink-cli pm +weekly-issues --project-id 123
|
||||
gitlink-cli pm +issue-tags --project-id 123
|
||||
|
||||
# 查询 PM 流水线和 Action 运行记录
|
||||
gitlink-cli pm +pipelines --project-id 123
|
||||
gitlink-cli pm +action-runs --project-id 123
|
||||
```
|
||||
|
||||
### 忽略文件模板
|
||||
|
||||
```bash
|
||||
|
|
@ -718,6 +737,7 @@ gitlink-cli/
|
|||
│ ├── org/ # 组织 shortcuts
|
||||
│ ├── ci/ # CI shortcuts
|
||||
│ ├── pipeline/ # Pipeline shortcuts
|
||||
│ ├── pm/ # 项目管理 shortcuts
|
||||
│ ├── search/ # 搜索 shortcuts
|
||||
│ ├── user/ # 用户 shortcuts
|
||||
│ └── register.go # 注册入口
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# PM shortcuts
|
||||
|
||||
## Background
|
||||
|
||||
The `gitlink-pm` Skill documented GitLink project-management workflows but had
|
||||
to call Raw API paths under `/pm/...` directly. That made Sprint reports, weekly
|
||||
issue summaries, board inspection, and PM pipeline checks less discoverable for
|
||||
users and agents.
|
||||
|
||||
This change adds a first-class `pm` shortcut group for read-only PM data.
|
||||
|
||||
## New shortcuts
|
||||
|
||||
- `pm +dashboards` lists PM dashboards for a project.
|
||||
- `pm +sprint-issues` lists Sprint issues.
|
||||
- `pm +weekly-issues` lists weekly issues.
|
||||
- `pm +issue-tags` lists PM issue tags.
|
||||
- `pm +pipelines` lists PM pipelines.
|
||||
- `pm +action-runs` lists PM action run records.
|
||||
|
||||
All commands require `--project-id` and accept `--page`/`--limit`.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
gitlink-cli pm +dashboards --project-id 123 --limit 20
|
||||
gitlink-cli pm +sprint-issues --project-id 123 --page 1 --limit 20
|
||||
gitlink-cli pm +weekly-issues --project-id 123
|
||||
gitlink-cli pm +issue-tags --project-id 123
|
||||
gitlink-cli pm +pipelines --project-id 123
|
||||
gitlink-cli pm +action-runs --project-id 123
|
||||
```
|
||||
|
||||
## Documentation updates
|
||||
|
||||
- README and README.zh-CN include PM usage examples.
|
||||
- `skills/gitlink-pm` now prefers `pm +...` shortcuts instead of Raw API calls.
|
||||
- The Skills overview lists the new PM commands.
|
||||
|
||||
## Tests
|
||||
|
||||
Unit tests cover:
|
||||
|
||||
- all PM shortcut endpoint mappings;
|
||||
- `project_id`, `page`, and `limit` query construction;
|
||||
- default pagination;
|
||||
- required `--project-id` validation;
|
||||
- HTTP error propagation.
|
||||
|
||||
Suggested verification:
|
||||
|
||||
```bash
|
||||
go test ./shortcuts/pm ./shortcuts
|
||||
```
|
||||
|
||||
Full project verification:
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
|
@ -56,6 +56,13 @@
|
|||
"cmd.org.list.short": "List organizations",
|
||||
"cmd.org.members.short": "List organization members",
|
||||
"cmd.org.short": "Organization operations",
|
||||
"cmd.pm.action_runs.short": "List PM action run records",
|
||||
"cmd.pm.dashboards.short": "List PM dashboards",
|
||||
"cmd.pm.issue_tags.short": "List PM issue tags",
|
||||
"cmd.pm.pipelines.short": "List PM pipelines",
|
||||
"cmd.pm.short": "Project management operations",
|
||||
"cmd.pm.sprint_issues.short": "List PM sprint issues",
|
||||
"cmd.pm.weekly_issues.short": "List PM weekly issues",
|
||||
"cmd.pr.close.short": "Close a pull request",
|
||||
"cmd.pr.comment.short": "Add a comment to a pull request",
|
||||
"cmd.pr.create.short": "Create a pull request",
|
||||
|
|
@ -189,6 +196,7 @@
|
|||
"flag.org.name": "Organization name",
|
||||
"flag.owner": "Repository owner (auto-detected from git remote)",
|
||||
"flag.page": "Page number",
|
||||
"flag.pm.project_id": "GitLink project ID",
|
||||
"flag.pr.assignee_id": "Assignee user ID",
|
||||
"flag.pr.base": "Target branch",
|
||||
"flag.pr.body": "PR description",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@
|
|||
"cmd.org.list.short": "列出组织",
|
||||
"cmd.org.members.short": "列出组织成员",
|
||||
"cmd.org.short": "组织操作",
|
||||
"cmd.pm.action_runs.short": "列出 PM Action 运行记录",
|
||||
"cmd.pm.dashboards.short": "列出 PM 看板",
|
||||
"cmd.pm.issue_tags.short": "列出 PM Issue 标签",
|
||||
"cmd.pm.pipelines.short": "列出 PM 流水线",
|
||||
"cmd.pm.short": "项目管理操作",
|
||||
"cmd.pm.sprint_issues.short": "列出 PM Sprint Issue",
|
||||
"cmd.pm.weekly_issues.short": "列出 PM 周报 Issue",
|
||||
"cmd.pr.close.short": "关闭拉取请求",
|
||||
"cmd.pr.comment.short": "给拉取请求添加评论",
|
||||
"cmd.pr.create.short": "创建拉取请求",
|
||||
|
|
@ -189,6 +196,7 @@
|
|||
"flag.org.name": "组织名称",
|
||||
"flag.owner": "仓库所有者(自动从 git remote 检测)",
|
||||
"flag.page": "页码",
|
||||
"flag.pm.project_id": "GitLink 项目 ID",
|
||||
"flag.pr.assignee_id": "指派人用户 ID",
|
||||
"flag.pr.base": "目标分支",
|
||||
"flag.pr.body": "PR 描述",
|
||||
|
|
|
|||
|
|
@ -1,137 +1,71 @@
|
|||
// Package pm implements GitLink project management shortcuts.
|
||||
package pm
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// Shortcuts returns project management shortcuts for GitLink.
|
||||
//
|
||||
// The pm domain provides commands for viewing dashboards, sprints,
|
||||
// weekly issues, tags, pipelines, and action runs associated with
|
||||
// a project.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
// Shortcuts returns project management read shortcuts.
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "dashboards",
|
||||
Description: "查看项目仪表盘数据",
|
||||
Flags: []common.Flag{
|
||||
{Name: "project", Usage: "项目 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
project, err := ctx.RequireArg("project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("project_id", project)
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/pm/dashboards", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
pmListShortcut(tr, "dashboards", tr.T("cmd.pm.dashboards.short"), "/pm/dashboards"),
|
||||
pmListShortcut(tr, "sprint-issues", tr.T("cmd.pm.sprint_issues.short"), "/pm/sprint_issues"),
|
||||
pmListShortcut(tr, "weekly-issues", tr.T("cmd.pm.weekly_issues.short"), "/pm/weekly_issues"),
|
||||
pmListShortcut(tr, "issue-tags", tr.T("cmd.pm.issue_tags.short"), "/pm/issue_tags"),
|
||||
pmListShortcut(tr, "pipelines", tr.T("cmd.pm.pipelines.short"), "/pm/pipelines"),
|
||||
pmListShortcut(tr, "action-runs", tr.T("cmd.pm.action_runs.short"), "/pm/action_runs"),
|
||||
}
|
||||
}
|
||||
|
||||
func pmListShortcut(tr *i18n.Translator, name, description, path string) *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Flags: []common.Flag{
|
||||
{Name: "project-id", Short: "P", Usage: tr.T("flag.pm.project_id"), Required: true},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
{
|
||||
Name: "sprints",
|
||||
Description: "查看 Sprint 任务列表",
|
||||
Flags: []common.Flag{
|
||||
{Name: "project", Usage: "项目 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
project, err := ctx.RequireArg("project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("project_id", project)
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/pm/sprint_issues", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "weekly",
|
||||
Description: "查看周报任务",
|
||||
Flags: []common.Flag{
|
||||
{Name: "project", Usage: "项目 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
project, err := ctx.RequireArg("project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("project_id", project)
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/pm/weekly_issues", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "tags",
|
||||
Description: "查看项目 Issue 标签",
|
||||
Flags: []common.Flag{
|
||||
{Name: "project", Usage: "项目 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
project, err := ctx.RequireArg("project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("project_id", project)
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/pm/issue_tags", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "pipelines",
|
||||
Description: "查看项目 CI/CD 流水线列表",
|
||||
Flags: []common.Flag{
|
||||
{Name: "project", Usage: "项目 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
project, err := ctx.RequireArg("project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("project_id", project)
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/pm/pipelines", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "runs",
|
||||
Description: "查看项目 Action 运行记录",
|
||||
Flags: []common.Flag{
|
||||
{Name: "project", Usage: "项目 ID", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
project, err := ctx.RequireArg("project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("project_id", project)
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/pm/action_runs", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
projectID, err := ctx.RequireArg("project-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := pmQuery(ctx, projectID)
|
||||
env, err := ctx.CallAPIWithQuery("GET", path, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func pmQuery(ctx *common.RuntimeContext, projectID string) url.Values {
|
||||
q := url.Values{}
|
||||
q.Set("project_id", strings.TrimSpace(projectID))
|
||||
q.Set("page", firstNonEmpty(ctx.Arg("page"), "1"))
|
||||
q.Set("limit", firstNonEmpty(ctx.Arg("limit"), "20"))
|
||||
return q
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,192 +4,26 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func TestPmDashboards(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockStatus int
|
||||
mockBody string
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{"正常返回", 200, `{"dashboards": []}`, 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)
|
||||
}
|
||||
if !strings.Contains(r.URL.Path, "/pm/dashboards") {
|
||||
t.Errorf("expected path containing /pm/dashboards, got %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("project_id"); got != "123" {
|
||||
t.Errorf("expected project_id=123, got %s", got)
|
||||
}
|
||||
w.WriteHeader(tt.mockStatus)
|
||||
w.Write([]byte(tt.mockBody))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "dashboards")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"project": "123"},
|
||||
}
|
||||
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 TestPmSprints(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)
|
||||
}
|
||||
if !strings.Contains(r.URL.Path, "/pm/sprint_issues") {
|
||||
t.Errorf("expected path containing /pm/sprint_issues, got %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"sprint_issues": []}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "sprints")
|
||||
func runShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
shortcut := findShortcut(t, name)
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"project": "456"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
t.Fatalf("sprints shortcut failed: %v", err)
|
||||
Format: "json",
|
||||
Args: args,
|
||||
Tr: i18n.Default(),
|
||||
}
|
||||
return shortcut.Run(ctx)
|
||||
}
|
||||
|
||||
func TestPmWeekly(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.URL.Path, "/pm/weekly_issues") {
|
||||
t.Errorf("expected path containing /pm/weekly_issues, got %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"weekly_issues": []}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "weekly")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"project": "789"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
t.Fatalf("weekly shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPmTags(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.URL.Path, "/pm/issue_tags") {
|
||||
t.Errorf("expected path containing /pm/issue_tags, got %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"issue_tags": []}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "tags")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"project": "100"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
t.Fatalf("tags shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPmPipelines(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.URL.Path, "/pm/pipelines") {
|
||||
t.Errorf("expected path containing /pm/pipelines, got %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"pipelines": []}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "pipelines")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"project": "200"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
t.Fatalf("pipelines shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPmRuns(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.URL.Path, "/pm/action_runs") {
|
||||
t.Errorf("expected path containing /pm/action_runs, got %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{"action_runs": []}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "runs")
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
Owner: "test", Repo: "test", Format: "json",
|
||||
Args: map[string]string{"project": "300"},
|
||||
}
|
||||
if err := shortcut.Run(ctx); err != nil {
|
||||
t.Fatalf("runs shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPmMissingProject(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("should not call API when --project is missing")
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
shortcut := findPmShortcut(t, "dashboards")
|
||||
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 err == nil {
|
||||
t.Fatal("expected error when --project is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func findPmShortcut(t *testing.T, name string) *common.Shortcut {
|
||||
func findShortcut(t *testing.T, name string) *common.Shortcut {
|
||||
t.Helper()
|
||||
for _, shortcut := range Shortcuts() {
|
||||
if shortcut.Name == name {
|
||||
|
|
@ -200,21 +34,100 @@ func findPmShortcut(t *testing.T, name string) *common.Shortcut {
|
|||
return nil
|
||||
}
|
||||
|
||||
func assertPmRequest(t *testing.T, r *http.Request, method, pathPrefix string) {
|
||||
func writeJSON(t *testing.T, w http.ResponseWriter, v interface{}) {
|
||||
t.Helper()
|
||||
if r.Method != method {
|
||||
t.Fatalf("got method %s, want %s", r.Method, method)
|
||||
}
|
||||
if !strings.HasPrefix(r.URL.Path, pathPrefix) {
|
||||
t.Fatalf("got path %s, want prefix %s", r.URL.Path, pathPrefix)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
t.Fatalf("write JSON: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func decodePmJSON(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 {
|
||||
t.Fatalf("failed to decode request body: %v", err)
|
||||
func TestPMReadShortcuts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{name: "dashboards", path: "/pm/dashboards.json"},
|
||||
{name: "sprint-issues", path: "/pm/sprint_issues.json"},
|
||||
{name: "weekly-issues", path: "/pm/weekly_issues.json"},
|
||||
{name: "issue-tags", path: "/pm/issue_tags.json"},
|
||||
{name: "pipelines", path: "/pm/pipelines.json"},
|
||||
{name: "action-runs", path: "/pm/action_runs.json"},
|
||||
}
|
||||
|
||||
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 != http.MethodGet {
|
||||
t.Fatalf("method = %s, want GET", r.Method)
|
||||
}
|
||||
if r.URL.Path != tt.path {
|
||||
t.Fatalf("path = %s, want %s", r.URL.Path, tt.path)
|
||||
}
|
||||
if got := r.URL.Query().Get("project_id"); got != "123" {
|
||||
t.Fatalf("project_id = %q, want 123", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "2" {
|
||||
t.Fatalf("page = %q, want 2", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "50" {
|
||||
t.Fatalf("limit = %q, want 50", got)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, tt.name, map[string]string{
|
||||
"project-id": "123",
|
||||
"page": "2",
|
||||
"limit": "50",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("%s failed: %v", tt.name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPMReadShortcutsDefaultPagination(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/pm/dashboards.json" {
|
||||
t.Fatalf("path = %s, want /pm/dashboards.json", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Fatalf("page = %q, want 1", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "20" {
|
||||
t.Fatalf("limit = %q, want 20", got)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "dashboards", map[string]string{"project-id": "123"}); err != nil {
|
||||
t.Fatalf("dashboards failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPMReadShortcutRequiresProjectID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("missing project-id should fail before remote API: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "dashboards", nil); err == nil {
|
||||
t.Fatal("expected missing project-id error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPMReadShortcutHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "dashboards", map[string]string{"project-id": "123"}); err == nil {
|
||||
t.Fatal("expected error for HTTP 500")
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"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"
|
||||
|
|
@ -43,6 +44,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
"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),
|
||||
|
|
@ -68,6 +70,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
"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"),
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ func TestRegisterAll(t *testing.T) {
|
|||
expectedGroups := []string{
|
||||
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
|
||||
"org", "user", "search", "ci", "workflow",
|
||||
"compare", "member", "milestone", "pipeline", "webhook",
|
||||
"dataset", "health", "ignore", "template",
|
||||
"compare", "member", "milestone", "pipeline", "pm", "webhook",
|
||||
"dataset", "health", "ignore", "wiki",
|
||||
}
|
||||
|
||||
groupSet := map[string]bool{}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ gitlink-cli auth status
|
|||
gitlink-cli user +me
|
||||
```
|
||||
|
||||
详见: [gitlink-shared/examples/auth-workflow.md](gitlink-shared/examples/auth-workflow.md)
|
||||
详见: [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md)
|
||||
|
||||
### 2. 查看可用命令
|
||||
|
||||
|
|
@ -64,25 +64,19 @@ skills/
|
|||
├── README.md # 本文件
|
||||
├── gitlink-shared/ # 共享基础规则
|
||||
│ ├── SKILL.md # 认证、全局参数、安全规则、分支约定
|
||||
│ ├── REFERENCE.md # API 详细参考、错误处理
|
||||
│ ├── TROUBLESHOOTING.md # 常见问题排查
|
||||
│ └── examples/
|
||||
│ └── auth-workflow.md # 认证工作流示例
|
||||
│ └── references/
|
||||
│ ├── api-reference.md # API 详细参考、错误处理
|
||||
│ ├── raw-api-batch.md # 批量 Raw API 调用参考
|
||||
│ └── troubleshooting.md # 常见问题排查
|
||||
├── gitlink-repo/ # 仓库管理
|
||||
│ ├── SKILL.md # 仓库操作指南
|
||||
│ ├── REFERENCE.md # 仓库 API 参考
|
||||
│ └── examples/
|
||||
│ └── repo-workflow.md # 仓库管理工作流
|
||||
│ └── references/ # 仓库命令参考文档
|
||||
├── gitlink-issue/ # Issue 管理
|
||||
│ ├── SKILL.md # Issue 操作指南
|
||||
│ ├── REFERENCE.md # Issue API 参考
|
||||
│ └── examples/
|
||||
│ └── issue-workflow.md # Issue 全流程工作流
|
||||
│ └── references/ # Issue 命令参考文档
|
||||
├── gitlink-pr/ # Pull Request
|
||||
│ ├── SKILL.md # PR 操作指南
|
||||
│ ├── REFERENCE.md # PR API 参考
|
||||
│ └── examples/
|
||||
│ └── pr-workflow.md # PR 工作流
|
||||
│ └── references/ # PR 命令参考文档
|
||||
├── gitlink-member/ # 仓库成员管理
|
||||
│ └── SKILL.md # 成员与邀请链接操作指南
|
||||
├── gitlink-branch/ # 分支管理
|
||||
|
|
@ -91,25 +85,24 @@ skills/
|
|||
│ └── branch-workflow.md # 分支工作流
|
||||
├── gitlink-release/ # 版本发布
|
||||
│ ├── SKILL.md # Release 操作指南
|
||||
│ ├── REFERENCE.md # Release API 参考
|
||||
│ └── examples/
|
||||
│ └── release-workflow.md # Release 工作流
|
||||
│ └── references/ # Release 命令参考文档
|
||||
├── gitlink-release-auto/ # 自动化 Release 管理
|
||||
│ └── SKILL.md # 自动发版、版本号推荐、Release Notes 生成
|
||||
├── gitlink-search/ # 搜索功能
|
||||
│ ├── SKILL.md # 搜索操作指南
|
||||
│ └── examples/
|
||||
│ └── search-workflow.md # 搜索工作流
|
||||
│ └── references/ # 搜索命令参考文档
|
||||
├── gitlink-user/ # 用户管理
|
||||
│ └── SKILL.md # 用户操作指南
|
||||
│ ├── SKILL.md # 用户操作指南
|
||||
│ └── references/ # 用户命令参考文档
|
||||
├── gitlink-org/ # 组织管理
|
||||
│ ├── SKILL.md # 组织操作指南
|
||||
│ └── examples/
|
||||
│ └── org-workflow.md # 组织工作流
|
||||
│ └── references/ # 组织命令参考文档
|
||||
├── gitlink-ci/ # CI/CD
|
||||
│ ├── SKILL.md # CI 操作指南
|
||||
│ └── examples/
|
||||
│ └── ci-workflow.md # CI 工作流
|
||||
│ └── SKILL.md # CI 操作指南
|
||||
├── gitlink-pipeline/ # 流水线工作流
|
||||
│ └── SKILL.md # Pipeline 操作指南
|
||||
├── gitlink-wiki/ # Wiki 页面管理
|
||||
│ └── SKILL.md # Wiki 操作指南
|
||||
├── gitlink-pm/ # 项目管理
|
||||
│ └── SKILL.md # PM 操作指南
|
||||
├── gitlink-health/ # 项目健康度分析
|
||||
|
|
@ -135,8 +128,8 @@ skills/
|
|||
|-------|------|----------|
|
||||
| **gitlink-shared** | 认证、全局参数、API 参考、安全规则、分支约定 | `auth login`, `auth status` |
|
||||
| **gitlink-repo** | 仓库管理与洞察 | `repo +list`, `repo +info`, `repo +languages`, `repo +contributors`, `repo +code-stats`, `repo +follow`, `repo +like` |
|
||||
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close`, `issue +comments` |
|
||||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +versions`, `pr +version-diff`, `pr +reviews`, `pr +review`, `pr +review-comments` |
|
||||
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close`, `issue +batch-update`, `issue +batch-delete` |
|
||||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +versions`, `pr +version-diff`, `pr +reviews`, `pr +review` |
|
||||
| **gitlink-member** | 仓库成员管理 | `member +list`, `member +add`, `member +batch-add`, `member +role`, `member +invite-link` |
|
||||
| **gitlink-branch** | 分支管理 | `branch +list`, `branch +create`, `branch +delete`, `branch +protect` |
|
||||
| **gitlink-release** | 版本发布 | `release +list`, `release +create`, `release +edit`, `release +update`, `release +view` |
|
||||
|
|
@ -150,7 +143,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-pm** | 项目管理 | 通过 Raw API 访问 |
|
||||
| **gitlink-wiki** | Wiki 页面管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` |
|
||||
| **gitlink-pm** | 项目管理 | `pm +dashboards`, `pm +sprint-issues`, `pm +weekly-issues`, `pm +action-runs` |
|
||||
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
|
||||
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
|
||||
|
||||
|
|
@ -169,7 +163,7 @@ gitlink-cli repo +info
|
|||
gitlink-cli repo +info --owner wbtiger --repo gitlink-cli
|
||||
```
|
||||
|
||||
详见: [gitlink-repo/examples/repo-workflow.md](gitlink-repo/examples/repo-workflow.md)
|
||||
详见: [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md)
|
||||
|
||||
### 场景 2:创建和管理 Issue
|
||||
|
||||
|
|
@ -190,7 +184,7 @@ gitlink-cli issue +close -i 123
|
|||
gitlink-cli issue +batch-close --numbers 123,124 --dry-run
|
||||
```
|
||||
|
||||
详见: [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md)
|
||||
详见: [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md)
|
||||
|
||||
### 场景 3:管理分支和发布
|
||||
|
||||
|
|
@ -208,7 +202,7 @@ gitlink-cli release +create -t v1.0.0 -n "v1.0.0 正式版" -b "更新内容..."
|
|||
gitlink-cli release +view -i <version_id>
|
||||
```
|
||||
|
||||
详见: [gitlink-release/examples/release-workflow.md](gitlink-release/examples/release-workflow.md)
|
||||
详见: [gitlink-release/SKILL.md](gitlink-release/SKILL.md)
|
||||
|
||||
### 场景 4:搜索和发现
|
||||
|
||||
|
|
@ -224,7 +218,7 @@ gitlink-cli org +list
|
|||
gitlink-cli org +info -i Gitlink
|
||||
```
|
||||
|
||||
详见: [gitlink-search/examples/search-workflow.md](gitlink-search/examples/search-workflow.md)
|
||||
详见: [gitlink-search/SKILL.md](gitlink-search/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -233,8 +227,8 @@ gitlink-cli org +info -i Gitlink
|
|||
### 快速查找
|
||||
|
||||
- **我想了解认证**: [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md)
|
||||
- **我想查看 API 细节**: [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
|
||||
- **我遇到了错误**: [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
|
||||
- **我想查看 API 细节**: [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
|
||||
- **我遇到了错误**: [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
|
||||
- **我想看工作流示例**: 查看各 Skill 下的 `examples/` 目录
|
||||
|
||||
### 按功能分类
|
||||
|
|
@ -242,12 +236,12 @@ gitlink-cli org +info -i Gitlink
|
|||
**仓库操作**:
|
||||
- [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md) - 仓库命令
|
||||
- [gitlink-branch/SKILL.md](gitlink-branch/SKILL.md) - 分支命令
|
||||
- [gitlink-repo/examples/repo-workflow.md](gitlink-repo/examples/repo-workflow.md) - 完整工作流
|
||||
- [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md) - 完整工作流
|
||||
|
||||
**Issue 和 PR**:
|
||||
- [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md) - Issue 命令
|
||||
- [gitlink-pr/SKILL.md](gitlink-pr/SKILL.md) - PR 命令
|
||||
- [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md) - Issue 工作流
|
||||
- [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md) - Issue 工作流
|
||||
|
||||
**发布和搜索**:
|
||||
- [gitlink-release/SKILL.md](gitlink-release/SKILL.md) - Release 命令
|
||||
|
|
@ -289,11 +283,11 @@ gitlink-cli auth login
|
|||
|
||||
### Q: 如何查看完整的 API 参考?
|
||||
|
||||
A: 查看 [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
|
||||
A: 查看 [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
|
||||
|
||||
### Q: 遇到错误怎么办?
|
||||
|
||||
A: 查看 [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
|
||||
A: 查看 [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -329,7 +323,7 @@ AI 代理可以:
|
|||
- 所有边界情况处理正确
|
||||
- 完整的文档和示例
|
||||
|
||||
详见: [../doc/SKILLS_TEST_REPORT_2026-04-02.md](../doc/SKILLS_TEST_REPORT_2026-04-02.md)
|
||||
详见: [../doc/design.md](../doc/design.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -337,8 +331,7 @@ AI 代理可以:
|
|||
|
||||
- [主项目 README](../README.md) - gitlink-cli 项目说明
|
||||
- [设计文档](../doc/design.md) - 架构设计和开发计划
|
||||
- [测试报告](../doc/SKILLS_TEST_REPORT_2026-04-02.md) - 功能测试报告
|
||||
- [代码同步方案](../doc/CODE_SYNC_STRATEGY_FINAL.md) - GitHub ↔ GitLink 同步设计
|
||||
- [API 参考文档](../doc/gitlink_api_reference.md) - GitLink API 参考文档
|
||||
- [gitlink-bisync](https://www.gitlink.org.cn/wbtiger/gitlink-bisync) - 代码双向同步系统
|
||||
|
||||
---
|
||||
|
|
@ -346,8 +339,8 @@ AI 代理可以:
|
|||
## 📞 获取帮助
|
||||
|
||||
- **命令帮助**: `gitlink-cli <command> --help`
|
||||
- **故障排查**: [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
|
||||
- **API 参考**: [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
|
||||
- **故障排查**: [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
|
||||
- **API 参考**: [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
|
||||
- **工作流示例**: 查看各 Skill 下的 `examples/` 目录
|
||||
|
||||
---
|
||||
|
|
@ -355,7 +348,7 @@ AI 代理可以:
|
|||
## 🎓 下一步
|
||||
|
||||
1. 阅读 [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md) 了解基础
|
||||
2. 查看 [gitlink-shared/examples/auth-workflow.md](gitlink-shared/examples/auth-workflow.md) 完成认证
|
||||
2. 查看 [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md) 完成认证
|
||||
3. 根据需求选择相应的 Skill 文档
|
||||
4. 参考 `examples/` 目录中的工作流示例
|
||||
5. 使用 AI 代理自动化你的工作流
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitlink-pm
|
||||
version: 1.0.0
|
||||
version: 2.0.0
|
||||
description: "项目管理(PM):Sprint、看板、周报等项目管理功能。当用户需要使用 GitLink PM 功能时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
|
|
@ -10,37 +10,56 @@ metadata:
|
|||
|
||||
# gitlink-pm(项目管理)
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)。
|
||||
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。**
|
||||
|
||||
GitLink PM 模块提供敏捷项目管理能力,目前通过 Raw API 访问。
|
||||
GitLink PM 模块提供敏捷项目管理能力。优先使用 `pm` shortcut,不再直接拼接 `/pm/...` Raw API。
|
||||
|
||||
## API 端点
|
||||
|
||||
> 前缀:`/api/pm`
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 看板
|
||||
gitlink-cli api GET /pm/dashboards --query 'project_id=123'
|
||||
gitlink-cli pm +dashboards --project-id 123 --format json
|
||||
|
||||
# Sprint Issue 列表
|
||||
gitlink-cli api GET /pm/sprint_issues --query 'project_id=123'
|
||||
gitlink-cli pm +sprint-issues --project-id 123 --page 1 --limit 20 --format json
|
||||
|
||||
# 周报
|
||||
gitlink-cli api GET /pm/weekly_issues --query 'project_id=123'
|
||||
# 周报 Issue
|
||||
gitlink-cli pm +weekly-issues --project-id 123 --format json
|
||||
|
||||
# Issue 标签
|
||||
gitlink-cli api GET /pm/issue_tags --query 'project_id=123'
|
||||
# PM Issue 标签
|
||||
gitlink-cli pm +issue-tags --project-id 123 --format json
|
||||
|
||||
# 流水线
|
||||
gitlink-cli api GET /pm/pipelines --query 'project_id=123'
|
||||
# PM 流水线
|
||||
gitlink-cli pm +pipelines --project-id 123 --format json
|
||||
|
||||
# Action 运行记录
|
||||
gitlink-cli api GET /pm/action_runs --query 'project_id=123'
|
||||
gitlink-cli pm +action-runs --project-id 123 --format json
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--project-id, -P` | 是 | GitLink 数字项目 ID |
|
||||
| `--page, -p` | 否 | 页码,默认 `1` |
|
||||
| `--limit, -l` | 否 | 每页数量,默认 `20` |
|
||||
|
||||
## API 映射
|
||||
|
||||
| Shortcut | API |
|
||||
|----------|-----|
|
||||
| `pm +dashboards` | `GET /pm/dashboards` |
|
||||
| `pm +sprint-issues` | `GET /pm/sprint_issues` |
|
||||
| `pm +weekly-issues` | `GET /pm/weekly_issues` |
|
||||
| `pm +issue-tags` | `GET /pm/issue_tags` |
|
||||
| `pm +pipelines` | `GET /pm/pipelines` |
|
||||
| `pm +action-runs` | `GET /pm/action_runs` |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- PM 接口需要项目 ID(`project_id`),可通过 `repo +info` 获取
|
||||
- PM 功能需要项目开启 PM 模块
|
||||
- PM 接口需要项目 ID(`project_id`),可通过 `repo +info` 获取。
|
||||
- PM 功能需要项目开启 PM 模块。
|
||||
- 这些命令均为只读查询,适合 Agent 生成 Sprint 报告、看板摘要和项目管理周报。
|
||||
- 建议在 Agent 场景始终使用 `--format json`。
|
||||
|
|
|
|||
Loading…
Reference in New Issue