diff --git a/README.md b/README.md
index eeaa346..6feb19e 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](https://golang.org)
[](https://www.npmjs.com/package/@gitlink-ai/cli)
-The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/README.md).
+The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/).
**[中文文档](./README.zh-CN.md)**
@@ -78,19 +78,11 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
jiangtx
-
-
## Why gitlink-cli?
-- **Agent-Native Design** — Structured [Skills](./skills/README.md) out of the box, compatible with Claude Code, OpenClaw, and other AI platforms — Agents can operate GitLink with zero extra setup
+- **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
- **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
@@ -104,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 |
-| 🐛 Issue | Create, update, close, batch close/update/delete, comment on issues |
+| 🐛 Issue | Create, update, close, batch close, comment on issues |
| 🔖 Label | Create, list, update, delete issue labels |
| 🔀 PR | Create, merge, review pull requests, view changed files |
| 👥 Member | List, add, remove repository members, change roles, create and accept invite links |
@@ -114,13 +106,9 @@ 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 |
+| 👤 User | View profiles, manage public keys, inspect user statistics |
| 📋 PM | Sprint management, kanban boards, weekly reports |
-| 📝 Template | Manage project templates for issues and pull requests |
| 🤖 Workflow | AI-powered issue triage, PR review, release notes |
## Installation & Quick Start
@@ -228,17 +216,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
-# Search repository files and inspect commit history
-gitlink-cli repo +files --owner Gitlink --repo forgeplus --search README --ref master
-gitlink-cli repo +commits --owner Gitlink --repo forgeplus --ref master --limit 20
-gitlink-cli repo +commit-files --owner Gitlink --repo forgeplus --sha
-gitlink-cli repo +commit-diff --owner Gitlink --repo forgeplus --sha
-
-# Inspect repository tags
-gitlink-cli repo +tags --owner Gitlink --repo forgeplus --name v1 --only-name true
-gitlink-cli repo +tag --owner Gitlink --repo forgeplus --name v1.0.0
-gitlink-cli repo +delete-tag --owner Gitlink --repo forgeplus --name v1.0.0 --dry-run
-
# Show language breakdown
gitlink-cli repo +languages --owner Gitlink --repo forgeplus
@@ -262,16 +239,6 @@ gitlink-cli repo +unfollow --owner Gitlink --repo forgeplus --project-id 123
gitlink-cli repo +like --owner Gitlink --repo forgeplus
gitlink-cli repo +unlike --owner Gitlink --repo forgeplus --project-id 123
-# Preview and apply a multi-file commit
-gitlink-cli repo +batch-commit --owner me --repo proj \
- --branch master --message "docs: update guide" \
- --files 'update:README.md:# Updated;create:docs/demo.md:# Demo' \
- --dry-run
-gitlink-cli repo +batch-commit --owner me --repo proj \
- --branch master --message "docs: update guide" \
- --files 'update:README.md:# Updated;delete:old.md' \
- --yes
-
# Create a repository
gitlink-cli repo +create -n my-project -d "Project description"
@@ -296,28 +263,6 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
```
-### Wiki Management
-
-```bash
-# List wiki pages (table of contents)
-gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
-
-# View a wiki page by page name
-gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
-
-# Create a wiki page
-gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
- -n getting-started -t "Getting Started" -c "# Getting Started Guide"
-
-# Update a wiki page title and/or content
-gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title"
-gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# Updated content"
-gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title" -c "New content"
-
-# Delete a wiki page
-gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
-```
-
### Member Management
```bash
@@ -367,14 +312,6 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
# Batch close issues from a CSV file
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
-# Preview batch metadata update by API issue IDs
-# Note: --ids uses API issue IDs, not web URL issue numbers.
-gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --ids 101,102 --status-id 3 --priority-id 2 --dry-run
-
-# Destructive batch delete requires both dry-run first and --yes for real execution
-gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --dry-run
-gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --yes
-
# Add a comment
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Fixed"
@@ -418,30 +355,6 @@ gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
```
-### Template Management
-
-```bash
-# List project templates
-gitlink-cli template +list --owner Gitlink --repo forgeplus
-
-# Get template details
-gitlink-cli template +get --owner Gitlink --repo forgeplus -i 1
-
-# Create an issue template
-gitlink-cli template +create --owner Gitlink --repo forgeplus \
- -t "ProjectTemplates::Issue" -n "Bug Report" -c "## Description\n..."
-
-# Create a pull request template
-gitlink-cli template +create --owner Gitlink --repo forgeplus \
- -t "ProjectTemplates::PullRequest" -n "Feature PR" -c "## Summary\n..."
-
-# Update a template
-gitlink-cli template +update --owner Gitlink --repo forgeplus -i 1 -n "Updated Name"
-
-# Delete a template
-gitlink-cli template +delete --owner Gitlink --repo forgeplus -i 1
-```
-
### Pull Requests
```bash
@@ -466,9 +379,6 @@ gitlink-cli pr +reopen --owner Gitlink --repo forgeplus -i 42
# View changed files
gitlink-cli pr +files --owner Gitlink --repo forgeplus -i 42
-# View commits included in a PR
-gitlink-cli pr +commits --owner Gitlink --repo forgeplus -i 42
-
# List PR patchset versions
gitlink-cli pr +versions --owner Gitlink --repo forgeplus -i 42
@@ -557,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
```
-### 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
@@ -577,39 +477,22 @@ 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.
-`user` also provides contributor-oriented shortcuts for heatmaps, aggregate
-statistics, and project trends.
+### User Operations
```bash
-# Current authenticated user
+# Current authenticated user and detailed profile
gitlink-cli user +me
+gitlink-cli user +current --format json
-# User contribution heatmap and aggregate statistics
-gitlink-cli user +heatmap --user zhangsan --year 2026
-gitlink-cli user +statistics --user zhangsan --start-time 1704067200 --end-time 1735689600
+# Public key management
+gitlink-cli user +keys --page 1 --limit 20
+gitlink-cli user +key-create --title "work laptop" --key-file ~/.ssh/id_rsa.pub --dry-run
+gitlink-cli user +key-delete --id 7 --dry-run
-# Project trend data (short alias: user +trends)
-gitlink-cli user +project-trends --user zhangsan
-
-# 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
+# User statistics
+gitlink-cli user +activity --login zhangsan
+gitlink-cli user +headmap --login zhangsan --year 2026
+gitlink-cli user +develop --login zhangsan --start-time 1704067200 --end-time 1735689599
```
### Workflow Agent Commands
@@ -711,32 +594,6 @@ Safety:
- `workflow +pr-summary` does not comment, approve, reject, or merge pull requests.
- `workflow +repo-report` aggregates health, issue triage, and PR review summary signals without remote writes.
-### Dataset
-
-`dataset` manages and queries GitLink research datasets (title, description,
-paper content, license, owning project).
-
-```bash
-# List datasets for one or more projects (by numeric project ID)
-gitlink-cli dataset +list --ids 5988
-
-# View a repository's dataset and attachments
-gitlink-cli dataset +view --owner Gitlink --repo forgeplus
-
-# Create / update a repository's dataset (preview first with --dry-run)
-gitlink-cli dataset +create --owner me --repo proj -t "My dataset" -d "..." --license-id 359 --dry-run
-gitlink-cli dataset +update --owner me --repo proj -t "My dataset" -d "updated"
-
-# Delete a dataset attachment (destructive: preview, then confirm with --yes)
-gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --dry-run
-gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --yes
-```
-
-> Note: `dataset +list` (platform dataset query) is verified on production
-> gitlink.org.cn. The per-repo `+view`/`+create`/`+update` routes follow the
-> published OpenAPI contract but are not yet deployed on production (they return
-> 404 there); they will work once the platform enables them.
-
### Raw API
For endpoints not covered by shortcuts, use the Raw API directly:
@@ -793,13 +650,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, batch update/delete, comment, etc.) |
+| `gitlink-issue` | Issue operations (create, update, close, comment, etc.) |
| `gitlink-pr` | Pull request operations (create, merge, review, etc.) |
| `gitlink-member` | Repository member and invite link management |
| `gitlink-branch` | Branch management (create, delete, list, protect, unprotect) |
@@ -808,7 +665,7 @@ See [skills/README.md](./skills/README.md) for details.
| `gitlink-pipeline` | Pipeline workflow operations (runs, logs, enable, disable, delete, etc.) |
| `gitlink-search` | Search (repositories, users, etc.) |
| `gitlink-org` | Organization management (members, teams, etc.) |
-| `gitlink-user` | User management (profile info, heatmaps, statistics, project trends, etc.) |
+| `gitlink-user` | User management (profiles, public keys, statistics, etc.) |
| `gitlink-pm` | Project management (sprints, kanban, weekly reports, etc.) |
| `gitlink-workflow` | AI-powered workflows (issue triage, PR review, release notes, etc.) |
| `gitlink-health` | Project health analysis (PR/Issue metrics aggregation, health reports) |
@@ -863,7 +720,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
@@ -928,7 +785,7 @@ gitlink-cli uses Windows Credential Manager for secure token storage. If Credent
### Q: Where can I find the full API reference?
-See [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md).
+See [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md).
## License
diff --git a/README.zh-CN.md b/README.zh-CN.md
index ad9a845..a9d221a 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -5,7 +5,7 @@
[](https://golang.org)
[](https://www.npmjs.com/package/@gitlink-ai/cli)
-[GitLink(确实开源)](https://www.gitlink.org.cn) 官方 CLI 工具 — 为人类和 AI Agent 双重设计。支持 **macOS、Linux、Windows**,覆盖仓库管理、Issue 追踪、Pull Request、Webhook、成员协作、CI/CD 和 AI 自动化工作流,包含 40+ 命令和 AI Agent [Skills](./skills/README.md)。
+[GitLink(确实开源)](https://www.gitlink.org.cn) 官方 CLI 工具 — 为人类和 AI Agent 双重设计。支持 **macOS、Linux、Windows**,覆盖仓库管理、Issue 追踪、Pull Request、Webhook、成员协作、CI/CD 和 AI 自动化工作流,包含 40+ 命令和 AI Agent [Skills](./skills/)。
**[English](./README.md)**
@@ -78,19 +78,11 @@
jiangtx
-
-
## 为什么选择 gitlink-cli?
-- **Agent-Native 设计** — 开箱即用结构化 [Skills](./skills/README.md),兼容 Claude Code — Agent 零配置即可操作 GitLink
+- **Agent-Native 设计** — 开箱即用结构化 [Skills](./skills/),兼容 Claude Code — Agent 零配置即可操作 GitLink
- **广泛覆盖** — 仓库、Issue、PR、Webhook、成员、分支、Release、CI、Pipeline、组织、搜索、用户等常用工作流均提供高层命令
- **AI 友好 & 优化** — 每条命令都经过真实 Agent 测试,简洁参数、智能默认值、结构化输出
- **跨平台** — macOS、Linux、Windows (x64/arm64) 全支持,`npm` 一条命令安装
@@ -104,7 +96,7 @@
| 分类 | 能力 |
|------|------|
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
-| 🐛 Issue | 创建、更新、关闭、批量关闭/更新/删除、评论 Issue |
+| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
| 👥 成员 | 列出、添加、移除仓库成员,调整角色,生成和接受邀请链接 |
@@ -113,13 +105,9 @@
| 🏢 组织 | 管理组织、成员、团队 |
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
-| 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
| 🔍 搜索 | 搜索仓库、用户 |
-| 📊 数据集 | 按项目查询科研数据集 |
-| 👤 用户 | 查看用户资料和信息 |
-| 📊 画像 | 用户开发能力、角色定位、专业定位、近期活动、贡献热力图统计 |
+| 👤 用户 | 查看资料、管理 Public Keys、查询用户统计 |
| 📋 项目管理 | Sprint 管理、看板、周报 |
-| 📝 模板 | 管理 Issue 和 Pull Request 项目模板 |
| 🤖 工作流 | AI 驱动的 Issue 分类、PR Review、Release Notes |
## 安装与快速上手
@@ -239,17 +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
-# 搜索仓库文件并查看提交历史
-gitlink-cli repo +files --owner Gitlink --repo forgeplus --search README --ref master
-gitlink-cli repo +commits --owner Gitlink --repo forgeplus --ref master --limit 20
-gitlink-cli repo +commit-files --owner Gitlink --repo forgeplus --sha
-gitlink-cli repo +commit-diff --owner Gitlink --repo forgeplus --sha
-
-# 查看仓库标签
-gitlink-cli repo +tags --owner Gitlink --repo forgeplus --name v1 --only-name true
-gitlink-cli repo +tag --owner Gitlink --repo forgeplus --name v1.0.0
-gitlink-cli repo +delete-tag --owner Gitlink --repo forgeplus --name v1.0.0 --dry-run
-
# 查看语言占比
gitlink-cli repo +languages --owner Gitlink --repo forgeplus
@@ -273,16 +250,6 @@ gitlink-cli repo +unfollow --owner Gitlink --repo forgeplus --project-id 123
gitlink-cli repo +like --owner Gitlink --repo forgeplus
gitlink-cli repo +unlike --owner Gitlink --repo forgeplus --project-id 123
-# 预览并执行多文件提交
-gitlink-cli repo +batch-commit --owner me --repo proj \
- --branch master --message "docs: update guide" \
- --files 'update:README.md:# Updated;create:docs/demo.md:# Demo' \
- --dry-run
-gitlink-cli repo +batch-commit --owner me --repo proj \
- --branch master --message "docs: update guide" \
- --files 'update:README.md:# Updated;delete:old.md' \
- --yes
-
# 创建仓库
gitlink-cli repo +create -n my-project -d "项目描述"
@@ -307,28 +274,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
@@ -378,14 +323,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 "已修复"
@@ -428,30 +365,6 @@ gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
```
-### 模板管理
-
-```bash
-# 列出项目模板
-gitlink-cli template +list --owner Gitlink --repo forgeplus
-
-# 获取模板详情
-gitlink-cli template +get --owner Gitlink --repo forgeplus -i 1
-
-# 创建 Issue 模板
-gitlink-cli template +create --owner Gitlink --repo forgeplus \
- -t "ProjectTemplates::Issue" -n "Bug 报告" -c "## 描述\n..."
-
-# 创建 Pull Request 模板
-gitlink-cli template +create --owner Gitlink --repo forgeplus \
- -t "ProjectTemplates::PullRequest" -n "功能 PR" -c "## 概述\n..."
-
-# 更新模板
-gitlink-cli template +update --owner Gitlink --repo forgeplus -i 1 -n "新名称"
-
-# 删除模板
-gitlink-cli template +delete --owner Gitlink --repo forgeplus -i 1
-```
-
### Pull Request
```bash
@@ -476,9 +389,6 @@ gitlink-cli pr +reopen --owner Gitlink --repo forgeplus -i 42
# 查看 PR 变更文件
gitlink-cli pr +files --owner Gitlink --repo forgeplus -i 42
-# 查看 PR 包含的提交
-gitlink-cli pr +commits --owner Gitlink --repo forgeplus -i 42
-
# 查看 PR patchset/version 列表
gitlink-cli pr +versions --owner Gitlink --repo forgeplus -i 42
@@ -535,16 +445,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
@@ -555,61 +455,24 @@ gitlink-cli search +repos -k "machine learning"
gitlink-cli search +users -k "zhangsan"
```
-### 用户画像
-
-`profile` 暴露 GitLink 原生的用户画像统计(开发能力、角色定位、专业定位、近期活动、贡献热力图)。
-省略 `--user` 时默认使用当前认证用户。
-`user` 同时提供面向贡献者分析的热力图、聚合统计和项目趋势快捷入口。
+### 用户操作
```bash
-# 当前认证用户
+# 当前登录用户和详细资料
gitlink-cli user +me
+gitlink-cli user +current --format json
-# 用户贡献热力图和聚合统计
-gitlink-cli user +heatmap --user zhangsan --year 2026
-gitlink-cli user +statistics --user zhangsan --start-time 1704067200 --end-time 1735689600
+# Public Key 管理
+gitlink-cli user +keys --page 1 --limit 20
+gitlink-cli user +key-create --title "work laptop" --key-file ~/.ssh/id_rsa.pub --dry-run
+gitlink-cli user +key-delete --id 7 --dry-run
-# 项目趋势数据(短别名:user +trends)
-gitlink-cli user +project-trends --user zhangsan
-
-# 开发能力评分 + 语言分布
-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
+# 用户统计
+gitlink-cli user +activity --login zhangsan
+gitlink-cli user +headmap --login zhangsan --year 2026
+gitlink-cli user +develop --login zhangsan --start-time 1704067200 --end-time 1735689599
```
-### 数据集
-
-`dataset` 管理并查询 GitLink 科研数据集(标题、描述、论文内容、许可证、所属项目)。
-
-```bash
-# 按数字项目 ID 列出一个或多个项目的数据集
-gitlink-cli dataset +list --ids 5988
-
-# 查看仓库的数据集及其附件
-gitlink-cli dataset +view --owner Gitlink --repo forgeplus
-
-# 创建 / 更新仓库数据集(先用 --dry-run 预览)
-gitlink-cli dataset +create --owner me --repo proj -t "我的数据集" -d "..." --license-id 359 --dry-run
-gitlink-cli dataset +update --owner me --repo proj -t "我的数据集" -d "更新"
-
-# 删除数据集附件(破坏性:先预览,再用 --yes 确认)
-gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --dry-run
-gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --yes
-```
-
-> 注意:`dataset +list`(平台数据集查询)已在生产 gitlink.org.cn 验证可用。按仓库的 `+view`/`+create`/`+update` 遵循已发布的 OpenAPI 契约,但生产环境尚未部署(当前返回 404),待平台上线后即可生效。
### Raw API
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:
@@ -666,13 +529,13 @@ git push gitlink
`skills/` 目录包含 Claude Code Agent Skill 文件,支持 AI 自动化操作 GitLink 平台。
-详见 [skills/README.md](./skills/README.md)
+详见 [skills/README.md](skills/README.md)
| Skill | 说明 |
|-------|------|
| `gitlink-shared` | 认证、全局参数、安全规则、API 注意事项 |
| `gitlink-repo` | 仓库操作(创建、查看、删除、Fork、洞察数据等) |
-| `gitlink-issue` | Issue 操作(创建、更新、关闭、批量更新/删除、评论等) |
+| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
| `gitlink-pr` | Pull Request 操作(创建、合并、Review 等) |
| `gitlink-member` | 仓库成员与邀请链接管理 |
| `gitlink-release` | 发布管理(创建、编辑、更新、查看、删除等) |
@@ -680,7 +543,7 @@ git push gitlink
| `gitlink-ci` | CI/CD 操作(构建、日志等) |
| `gitlink-pipeline` | 流水线工作流操作(运行、日志、启停、删除等) |
| `gitlink-search` | 搜索功能(仓库、用户等) |
-| `gitlink-user` | 用户管理(个人信息、热力图、统计、项目趋势等) |
+| `gitlink-user` | 用户管理(资料、Public Keys、统计等) |
| `gitlink-pm` | 项目管理(Sprint、看板、周报等) |
| `gitlink-workflow` | AI 自动化工作流(Issue 分类、PR Review、Release Notes 等) |
@@ -734,7 +597,7 @@ gitlink-cli/
## 文档
-- [Skills 使用指南](./skills/README.md) — AI Agent Skills 详细说明
+- [Skills 使用指南](skills/README.md) — AI Agent Skills 详细说明
- [设计文档](doc/design.md) — 架构设计和开发计划
## 常见问题
@@ -797,7 +660,7 @@ gitlink-cli 使用 Windows Credential Manager 安全存储 Token。如果 Creden
### Q: 如何查看完整的 API 参考?
-查看 [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md)
+查看 [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md)
## 许可证
diff --git a/doc/changes/user-account-stats-shortcuts.md b/doc/changes/user-account-stats-shortcuts.md
new file mode 100644
index 0000000..8bec3e7
--- /dev/null
+++ b/doc/changes/user-account-stats-shortcuts.md
@@ -0,0 +1,36 @@
+# User Account And Stats Shortcuts
+
+Submitter: Wang Yue
+
+This change expands the `user` shortcut group with account metadata, Public Key management, and user statistics OpenAPI coverage.
+
+## Commands
+
+- `user +current`
+- `user +keys`
+- `user +key-create`
+- `user +key-delete`
+- `user +activity`
+- `user +headmap`
+- `user +develop`
+- `user +role`
+- `user +major`
+
+## API Mapping
+
+| Shortcut | Method | API path |
+|----------|--------|----------|
+| `user +current` | GET | `/api/users/get_user_info.json` |
+| `user +keys` | GET | `/api/public_keys.json` |
+| `user +key-create` | POST | `/api/public_keys.json` |
+| `user +key-delete` | DELETE | `/api/public_keys/{id}.json` |
+| `user +activity` | GET | `/api/users/{owner}/statistics/activity.json` |
+| `user +headmap` | GET | `/api/users/{owner}/headmaps.json` |
+| `user +develop` | GET | `/api/users/{owner}/statistics/develop.json` |
+| `user +role` | GET | `/api/users/{owner}/statistics/role.json` |
+| `user +major` | GET | `/api/users/{owner}/statistics/major.json` |
+
+## Verification
+
+- Unit tests cover public key list/create/delete requests, dry-run behavior, current profile lookup, user statistics endpoints, query parameters, and argument validation.
+- `user +key-create` supports either inline `--key` content or `--key-file`.
diff --git a/shortcuts/user/user.go b/shortcuts/user/user.go
index 4a2ba22..3b1eaf2 100644
--- a/shortcuts/user/user.go
+++ b/shortcuts/user/user.go
@@ -3,22 +3,15 @@ package user
import (
"fmt"
"net/url"
+ "os"
+ "strings"
"github.com/gitlink-org/gitlink-cli/internal/i18n"
- "github.com/gitlink-org/gitlink-cli/internal/output"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
tr := shortcutTranslator(translators...)
- userFlag := common.Flag{Name: "user", Short: "u", Usage: tr.T("flag.user")}
- yearFlag := common.Flag{Name: "year", Usage: tr.T("flag.user.year")}
- timeFlags := []common.Flag{
- {Name: "start-time", Usage: tr.T("flag.user.start_time")},
- {Name: "end-time", Usage: tr.T("flag.user.end_time")},
- }
- windowFlags := append([]common.Flag{userFlag}, timeFlags...)
-
return []*common.Shortcut{
{
Name: "me",
@@ -31,6 +24,17 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
return ctx.Output(env)
},
},
+ {
+ Name: "current",
+ Description: "Show current user profile details",
+ Run: func(ctx *common.RuntimeContext) error {
+ env, err := ctx.CallAPI("GET", "/users/get_user_info", nil)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
+ },
+ },
{
Name: "info",
Description: tr.T("cmd.user.info.short"),
@@ -50,19 +54,17 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
},
},
{
- Name: "heatmap",
- Description: tr.T("cmd.user.heatmap.short"),
- Flags: []common.Flag{userFlag, yearFlag},
+ Name: "keys",
+ Description: "List current user public keys",
+ Flags: []common.Flag{
+ {Name: "page", Short: "p", Usage: "Page number", Default: "1"},
+ {Name: "limit", Usage: "Items per page", Default: "20"},
+ },
Run: func(ctx *common.RuntimeContext) error {
- user, err := resolveUser(ctx)
- if err != nil {
- return err
- }
q := url.Values{}
- if v := ctx.Arg("year"); v != "" {
- q.Set("year", v)
- }
- env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/users/%s/headmaps", user), q)
+ setQueryIfPresent(q, ctx, "page", "page")
+ setQueryIfPresent(q, ctx, "limit", "limit")
+ env, err := ctx.CallAPIWithQuery("GET", "/public_keys", q)
if err != nil {
return err
}
@@ -70,82 +72,142 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
},
},
{
- Name: "statistics",
- Description: tr.T("cmd.user.statistics.short"),
- Flags: windowFlags,
+ Name: "key-create",
+ Description: "Create a public key",
+ Flags: []common.Flag{
+ {Name: "title", Short: "t", Usage: "Public key title", Required: true},
+ {Name: "key", Short: "k", Usage: "Public key content"},
+ {Name: "key-file", Usage: "Path to a public key file"},
+ {Name: "dry-run", Usage: "Preview the create request without adding a key", Bool: true, Default: "false"},
+ },
Run: func(ctx *common.RuntimeContext) error {
- return runWindowedUserGet(ctx, "/users/%s/statistics")
+ title, err := ctx.RequireArg("title")
+ if err != nil {
+ return err
+ }
+ key, err := publicKeyFromArgs(ctx)
+ if err != nil {
+ return err
+ }
+ payload := map[string]interface{}{
+ "title": title,
+ "key": key,
+ }
+ path := "/public_keys"
+ if ctx.Arg("dry-run") == "true" {
+ return ctx.OutputData(map[string]interface{}{
+ "dry_run": true,
+ "action": "create_public_key",
+ "method": "POST",
+ "path": path,
+ "payload": payload,
+ })
+ }
+ env, err := ctx.CallAPI("POST", path, payload)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
},
},
{
- Name: "stats",
- Description: tr.T("cmd.user.stats.short"),
- Flags: windowFlags,
+ Name: "key-delete",
+ Description: "Delete a public key",
+ Flags: []common.Flag{
+ {Name: "id", Short: "i", Usage: "Public key ID", Required: true},
+ {Name: "dry-run", Usage: "Preview the delete request without removing a key", Bool: true, Default: "false"},
+ },
Run: func(ctx *common.RuntimeContext) error {
- return runWindowedUserGet(ctx, "/users/%s/statistics")
+ id, err := ctx.RequireArg("id")
+ if err != nil {
+ return err
+ }
+ path := fmt.Sprintf("/public_keys/%s", url.PathEscape(id))
+ if ctx.Arg("dry-run") == "true" {
+ return ctx.OutputData(map[string]interface{}{
+ "dry_run": true,
+ "action": "delete_public_key",
+ "method": "DELETE",
+ "path": path,
+ })
+ }
+ env, err := ctx.CallAPI("DELETE", path, nil)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
},
},
- {
- Name: "project-trends",
- Description: tr.T("cmd.user.project_trends.short"),
- Flags: windowFlags,
- Run: func(ctx *common.RuntimeContext) error {
- return runWindowedUserGet(ctx, "/users/%s/project_trends")
- },
- },
- {
- Name: "trends",
- Description: tr.T("cmd.user.trends.short"),
- Flags: windowFlags,
- Run: func(ctx *common.RuntimeContext) error {
- return runWindowedUserGet(ctx, "/users/%s/project_trends")
- },
+ userStatsShortcut("activity", "Show user recent activity statistics", "statistics/activity", false, false),
+ userStatsShortcut("headmap", "Show user contribution heatmap", "headmaps", true, false),
+ userStatsShortcut("develop", "Show user development ability statistics", "statistics/develop", false, true),
+ userStatsShortcut("role", "Show user role statistics", "statistics/role", false, true),
+ userStatsShortcut("major", "Show user major statistics", "statistics/major", false, true),
+ }
+}
+
+func userStatsShortcut(name, description, endpoint string, withYear, withTimeRange bool) *common.Shortcut {
+ flags := []common.Flag{
+ {Name: "login", Short: "l", Usage: "User login name", Required: true},
+ }
+ if withYear {
+ flags = append(flags, common.Flag{Name: "year", Usage: "Contribution year"})
+ }
+ if withTimeRange {
+ flags = append(flags,
+ common.Flag{Name: "start-time", Usage: "Start timestamp"},
+ common.Flag{Name: "end-time", Usage: "End timestamp"},
+ )
+ }
+ return &common.Shortcut{
+ Name: name,
+ Description: description,
+ Flags: flags,
+ Run: func(ctx *common.RuntimeContext) error {
+ login, err := ctx.RequireArg("login")
+ if err != nil {
+ return err
+ }
+ q := url.Values{}
+ setQueryIfPresent(q, ctx, "year", "year")
+ setQueryIfPresent(q, ctx, "start-time", "start_time")
+ setQueryIfPresent(q, ctx, "end-time", "end_time")
+ path := fmt.Sprintf("/users/%s/%s", url.PathEscape(login), endpoint)
+ env, err := ctx.CallAPIWithQuery("GET", path, q)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
},
}
}
-func runWindowedUserGet(ctx *common.RuntimeContext, pathFormat string) error {
- user, err := resolveUser(ctx)
- if err != nil {
- return err
+func publicKeyFromArgs(ctx *common.RuntimeContext) (string, error) {
+ key := strings.TrimSpace(ctx.Arg("key"))
+ keyFile := strings.TrimSpace(ctx.Arg("key-file"))
+ if key != "" && keyFile != "" {
+ return "", fmt.Errorf("use only one of --key or --key-file")
}
- q := url.Values{}
- if v := ctx.Arg("start-time"); v != "" {
- q.Set("start_time", v)
+ if key == "" && keyFile == "" {
+ return "", fmt.Errorf("required flag --key or --key-file is missing")
}
- if v := ctx.Arg("end-time"); v != "" {
- q.Set("end_time", v)
+ if keyFile != "" {
+ data, err := os.ReadFile(keyFile)
+ if err != nil {
+ return "", fmt.Errorf("read --key-file: %w", err)
+ }
+ key = strings.TrimSpace(string(data))
}
- env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf(pathFormat, user), q)
- if err != nil {
- return err
+ if key == "" {
+ return "", fmt.Errorf("public key content is empty")
}
- return ctx.Output(env)
+ return key, nil
}
-func resolveUser(ctx *common.RuntimeContext) (string, error) {
- if v := ctx.Arg("user"); v != "" {
- return v, nil
+func setQueryIfPresent(q url.Values, ctx *common.RuntimeContext, flagName, queryName string) {
+ if value := ctx.Arg(flagName); value != "" {
+ q.Set(queryName, value)
}
- env, err := ctx.CallAPI("GET", "/users/me", nil)
- if err != nil {
- return "", err
- }
- if login := extractLogin(env); login != "" {
- return login, nil
- }
- return "", fmt.Errorf("%s", ctx.Tr.T("error.user.required"))
-}
-
-func extractLogin(env *output.Envelope) string {
- data, ok := env.Data.(map[string]interface{})
- if !ok {
- return ""
- }
- if v, ok := data["login"].(string); ok {
- return v
- }
- return ""
}
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
diff --git a/shortcuts/user/user_test.go b/shortcuts/user/user_test.go
index 51d931a..927d870 100644
--- a/shortcuts/user/user_test.go
+++ b/shortcuts/user/user_test.go
@@ -2,276 +2,328 @@ package user
import (
"encoding/json"
+ "fmt"
"net/http"
"net/http/httptest"
+ "reflect"
"testing"
"github.com/gitlink-org/gitlink-cli/internal/client"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
-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: "owner",
- Repo: "repo",
- Format: "json",
- Args: args,
- }
- return shortcut.Run(ctx)
-}
-
-func findShortcut(t *testing.T, name string) *common.Shortcut {
- t.Helper()
- for _, s := range Shortcuts() {
- if s.Name == name {
- return s
- }
- }
- t.Fatalf("shortcut %q not found", name)
- return nil
-}
-
-func writeJSON(w http.ResponseWriter, v interface{}) {
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(v)
-}
-
-// --- me ---
-
func TestUserMe(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/me.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- writeJSON(w, map[string]interface{}{
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "GET", "/users/me.json")
+ writeUserJSON(t, w, map[string]interface{}{
"login": "currentuser",
"name": "Current User",
"id": float64(1),
})
- }))
+ })
defer server.Close()
- err := runShortcut(t, server, "me", nil)
- if err != nil {
- t.Fatalf("me failed: %v", err)
+ if err := runUserShortcut(t, server, "me", nil); err != nil {
+ t.Fatalf("me shortcut failed: %v", err)
}
}
-// --- info ---
+func TestUserCurrent(t *testing.T) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "GET", "/users/get_user_info.json")
+ writeUserJSON(t, w, map[string]interface{}{"login": "alice"})
+ })
+ defer server.Close()
+
+ if err := runUserShortcut(t, server, "current", nil); err != nil {
+ t.Fatalf("current shortcut failed: %v", err)
+ }
+}
func TestUserInfo(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/alice.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- writeJSON(w, map[string]interface{}{
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "GET", "/users/alice.json")
+ writeUserJSON(t, w, map[string]interface{}{
"login": "alice",
"name": "Alice",
})
- }))
+ })
defer server.Close()
- err := runShortcut(t, server, "info", map[string]string{"login": "alice"})
+ if err := runUserShortcut(t, server, "info", map[string]string{"login": "alice"}); err != nil {
+ t.Fatalf("info shortcut failed: %v", err)
+ }
+}
+
+func TestUserKeys(t *testing.T) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "GET", "/public_keys.json")
+ assertUserQuery(t, r, "page", "2")
+ assertUserQuery(t, r, "limit", "50")
+ writeUserJSON(t, w, map[string]interface{}{"total_count": 0, "public_keys": []interface{}{}})
+ })
+ defer server.Close()
+
+ err := runUserShortcut(t, server, "keys", map[string]string{
+ "page": "2",
+ "limit": "50",
+ })
if err != nil {
- t.Fatalf("info failed: %v", err)
+ t.Fatalf("keys shortcut failed: %v", err)
+ }
+}
+
+func TestUserKeyCreatePayload(t *testing.T) {
+ var payload map[string]interface{}
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "POST", "/public_keys.json")
+ payload = decodeUserJSON(t, r)
+ writeUserJSON(t, w, map[string]interface{}{"id": 1})
+ })
+ defer server.Close()
+
+ err := runUserShortcut(t, server, "key-create", map[string]string{
+ "title": "work laptop",
+ "key": "ssh-rsa AAAA test@example.com",
+ })
+ if err != nil {
+ t.Fatalf("key-create shortcut failed: %v", err)
+ }
+
+ assertUserEqual(t, payload["title"], "work laptop")
+ assertUserEqual(t, payload["key"], "ssh-rsa AAAA test@example.com")
+}
+
+func TestUserKeyDelete(t *testing.T) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "DELETE", "/public_keys/7.json")
+ writeUserJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
+ })
+ defer server.Close()
+
+ if err := runUserShortcut(t, server, "key-delete", map[string]string{"id": "7"}); err != nil {
+ t.Fatalf("key-delete shortcut failed: %v", err)
+ }
+}
+
+func TestUserKeyDryRunDoesNotCallAPI(t *testing.T) {
+ dryRunCases := []struct {
+ name string
+ args map[string]string
+ }{
+ {name: "key-create", args: map[string]string{"title": "work laptop", "key": "ssh-rsa AAAA", "dry-run": "true"}},
+ {name: "key-delete", args: map[string]string{"id": "7", "dry-run": "true"}},
+ }
+
+ for _, tc := range dryRunCases {
+ t.Run(tc.name, func(t *testing.T) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("dry-run should not call API, got %s %s", r.Method, r.URL.Path)
+ })
+ defer server.Close()
+
+ if err := runUserShortcut(t, server, tc.name, tc.args); err != nil {
+ t.Fatalf("%s dry-run failed: %v", tc.name, err)
+ }
+ })
+ }
+}
+
+func TestUserStatsShortcuts(t *testing.T) {
+ tests := []struct {
+ name string
+ args map[string]string
+ path string
+ queryKey string
+ queryValue string
+ queryKey2 string
+ queryValue2 string
+ }{
+ {name: "activity", args: map[string]string{"login": "alice"}, path: "/users/alice/statistics/activity.json"},
+ {name: "headmap", args: map[string]string{"login": "alice", "year": "2026"}, path: "/users/alice/headmaps.json", queryKey: "year", queryValue: "2026"},
+ {name: "develop", args: map[string]string{"login": "alice", "start-time": "100", "end-time": "200"}, path: "/users/alice/statistics/develop.json", queryKey: "start_time", queryValue: "100", queryKey2: "end_time", queryValue2: "200"},
+ {name: "role", args: map[string]string{"login": "alice", "start-time": "100", "end-time": "200"}, path: "/users/alice/statistics/role.json", queryKey: "start_time", queryValue: "100", queryKey2: "end_time", queryValue2: "200"},
+ {name: "major", args: map[string]string{"login": "alice", "start-time": "100", "end-time": "200"}, path: "/users/alice/statistics/major.json", queryKey: "start_time", queryValue: "100", queryKey2: "end_time", queryValue2: "200"},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ assertUserRequest(t, r, "GET", tc.path)
+ if tc.queryKey != "" {
+ assertUserQuery(t, r, tc.queryKey, tc.queryValue)
+ }
+ if tc.queryKey2 != "" {
+ assertUserQuery(t, r, tc.queryKey2, tc.queryValue2)
+ }
+ writeUserJSON(t, w, map[string]interface{}{"status": 0})
+ })
+ defer server.Close()
+
+ if err := runUserShortcut(t, server, tc.name, tc.args); err != nil {
+ t.Fatalf("%s shortcut failed: %v", tc.name, err)
+ }
+ })
}
}
func TestUserInfoMissingLogin(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- t.Fatal("no API call expected")
- }))
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("missing login should not call API")
+ })
defer server.Close()
- err := runShortcut(t, server, "info", map[string]string{})
+ err := runUserShortcut(t, server, "info", map[string]string{})
if err == nil {
t.Fatal("expected error for missing login")
}
}
-// --- heatmap ---
-
-func TestUserHeatmapExplicitUserWithYear(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/alice/headmaps.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- if got := r.URL.Query().Get("year"); got != "2026" {
- t.Fatalf("year = %q, want 2026", got)
- }
- writeJSON(w, map[string]interface{}{
- "total_contributions": float64(12),
- })
- }))
+func TestUserKeyCreateRejectsMissingKey(t *testing.T) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("missing key should not call API, got %s %s", r.Method, r.URL.Path)
+ })
defer server.Close()
- err := runShortcut(t, server, "heatmap", map[string]string{"user": "alice", "year": "2026"})
- if err != nil {
- t.Fatalf("heatmap failed: %v", err)
- }
-}
-
-func TestUserHeatmapDefaultsToCurrentUser(t *testing.T) {
- var calls []string
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- calls = append(calls, r.URL.Path)
- switch r.URL.Path {
- case "/users/me.json":
- writeJSON(w, map[string]interface{}{"login": "currentuser"})
- case "/users/currentuser/headmaps.json":
- writeJSON(w, map[string]interface{}{"headmaps": []interface{}{}})
- default:
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- }))
- defer server.Close()
-
- err := runShortcut(t, server, "heatmap", map[string]string{})
- if err != nil {
- t.Fatalf("heatmap failed: %v", err)
- }
- if len(calls) != 2 {
- t.Fatalf("calls = %v, want 2 calls", calls)
- }
-}
-
-// --- statistics ---
-
-func TestUserStatisticsWithTimeWindow(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/alice/statistics.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- if got := r.URL.Query().Get("start_time"); got != "100" {
- t.Fatalf("start_time = %q, want 100", got)
- }
- if got := r.URL.Query().Get("end_time"); got != "200" {
- t.Fatalf("end_time = %q, want 200", got)
- }
- writeJSON(w, map[string]interface{}{
- "issues_count": float64(3),
- })
- }))
- defer server.Close()
-
- args := map[string]string{"user": "alice", "start-time": "100", "end-time": "200"}
- err := runShortcut(t, server, "statistics", args)
- if err != nil {
- t.Fatalf("statistics failed: %v", err)
- }
-}
-
-func TestUserStatsAlias(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/alice/statistics.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- writeJSON(w, map[string]interface{}{})
- }))
- defer server.Close()
-
- err := runShortcut(t, server, "stats", map[string]string{"user": "alice"})
- if err != nil {
- t.Fatalf("stats alias failed: %v", err)
- }
-}
-
-// --- project trends ---
-
-func TestUserProjectTrendsWithTimeWindow(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/alice/project_trends.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- if got := r.URL.Query().Get("start_time"); got != "100" {
- t.Fatalf("start_time = %q, want 100", got)
- }
- if got := r.URL.Query().Get("end_time"); got != "200" {
- t.Fatalf("end_time = %q, want 200", got)
- }
- writeJSON(w, map[string]interface{}{
- "trends": []interface{}{},
- })
- }))
- defer server.Close()
-
- args := map[string]string{"user": "alice", "start-time": "100", "end-time": "200"}
- err := runShortcut(t, server, "project-trends", args)
- if err != nil {
- t.Fatalf("project-trends failed: %v", err)
- }
-}
-
-func TestUserTrendsAlias(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/alice/project_trends.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- writeJSON(w, map[string]interface{}{})
- }))
- defer server.Close()
-
- err := runShortcut(t, server, "trends", map[string]string{"user": "alice"})
- if err != nil {
- t.Fatalf("trends alias failed: %v", err)
- }
-}
-
-func TestUserDefaultUserMissingLogin(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/users/me.json" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
- }
- writeJSON(w, map[string]interface{}{"name": "no login"})
- }))
- defer server.Close()
-
- err := runShortcut(t, server, "statistics", map[string]string{})
+ err := runUserShortcut(t, server, "key-create", map[string]string{"title": "work laptop"})
if err == nil {
- t.Fatal("expected error when /users/me has no login")
+ t.Fatal("expected missing key to return an error")
}
}
-// --- HTTP error paths ---
-
func TestUserMeHTTPError(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
- w.Write([]byte("server error"))
- }))
+ if _, err := w.Write([]byte("server error")); err != nil {
+ t.Fatalf("write response: %v", err)
+ }
+ })
defer server.Close()
- err := runShortcut(t, server, "me", nil)
- if err == nil {
- t.Fatal("expected error for HTTP 500")
- }
-}
-
-func TestUserHeatmapHTTPError(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()
-
- err := runShortcut(t, server, "heatmap", map[string]string{"user": "alice"})
+ err := runUserShortcut(t, server, "me", nil)
if err == nil {
t.Fatal("expected error for HTTP 500")
}
}
func TestUserInfoHTTPError(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ server := newUserTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
- w.Write([]byte("server error"))
- }))
+ if _, err := w.Write([]byte("server error")); err != nil {
+ t.Fatalf("write response: %v", err)
+ }
+ })
defer server.Close()
- err := runShortcut(t, server, "info", map[string]string{"login": "alice"})
+ err := runUserShortcut(t, server, "info", map[string]string{"login": "alice"})
if err == nil {
t.Fatal("expected error for HTTP 500")
}
}
+
+func TestUserShortcutNames(t *testing.T) {
+ got := map[string]bool{}
+ for _, shortcut := range Shortcuts() {
+ got[shortcut.Name] = true
+ }
+ want := []string{"me", "current", "info", "keys", "key-create", "key-delete", "activity", "headmap", "develop", "role", "major"}
+ for _, name := range want {
+ if !got[name] {
+ t.Fatalf("missing shortcut %q in %v", name, got)
+ }
+ }
+ if len(got) != len(want) {
+ t.Fatalf("shortcut count = %d, want %d: %v", len(got), len(want), got)
+ }
+}
+
+func runUserShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
+ t.Helper()
+ shortcut := findUserShortcut(t, name)
+ ctx := &common.RuntimeContext{
+ Client: &client.Client{
+ HTTP: server.Client(),
+ BaseURL: server.URL,
+ },
+ Owner: "owner",
+ Repo: "repo",
+ Format: "json",
+ Args: args,
+ }
+ if ctx.Args == nil {
+ ctx.Args = map[string]string{}
+ }
+ return shortcut.Run(ctx)
+}
+
+func findUserShortcut(t *testing.T, name string) *common.Shortcut {
+ t.Helper()
+ for _, shortcut := range Shortcuts() {
+ if shortcut.Name == name {
+ return shortcut
+ }
+ }
+ t.Fatalf("shortcut %q not found", name)
+ return nil
+}
+
+func newUserTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
+ t.Helper()
+ return httptest.NewServer(handler)
+}
+
+func assertUserRequest(t *testing.T, r *http.Request, method, path string) {
+ t.Helper()
+ if r.Method != method || r.URL.Path != path {
+ t.Fatalf("got request %s %s, want %s %s", r.Method, r.URL.Path, method, path)
+ }
+}
+
+func assertUserQuery(t *testing.T, r *http.Request, key, want string) {
+ t.Helper()
+ if got := r.URL.Query().Get(key); got != want {
+ t.Fatalf("query %s = %q, want %q", key, got, want)
+ }
+}
+
+func decodeUserJSON(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)
+ }
+ return payload
+}
+
+func writeUserJSON(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 assertUserEqual(t *testing.T, got interface{}, want interface{}) {
+ t.Helper()
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
+ }
+}
+
+func ExampleShortcuts() {
+ for _, shortcut := range Shortcuts() {
+ fmt.Println(shortcut.Name)
+ }
+ // Output:
+ // me
+ // current
+ // info
+ // keys
+ // key-create
+ // key-delete
+ // activity
+ // headmap
+ // develop
+ // role
+ // major
+}
diff --git a/skills/README.md b/skills/README.md
index 6735c07..487239c 100644
--- a/skills/README.md
+++ b/skills/README.md
@@ -32,7 +32,7 @@ gitlink-cli auth status
gitlink-cli user +me
```
-详见: [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md)
+详见: [gitlink-shared/examples/auth-workflow.md](gitlink-shared/examples/auth-workflow.md)
### 2. 查看可用命令
@@ -64,19 +64,25 @@ skills/
├── README.md # 本文件
├── gitlink-shared/ # 共享基础规则
│ ├── SKILL.md # 认证、全局参数、安全规则、分支约定
-│ └── references/
-│ ├── api-reference.md # API 详细参考、错误处理
-│ ├── raw-api-batch.md # 批量 Raw API 调用参考
-│ └── troubleshooting.md # 常见问题排查
+│ ├── REFERENCE.md # API 详细参考、错误处理
+│ ├── TROUBLESHOOTING.md # 常见问题排查
+│ └── examples/
+│ └── auth-workflow.md # 认证工作流示例
├── gitlink-repo/ # 仓库管理
│ ├── SKILL.md # 仓库操作指南
-│ └── references/ # 仓库命令参考文档
+│ ├── REFERENCE.md # 仓库 API 参考
+│ └── examples/
+│ └── repo-workflow.md # 仓库管理工作流
├── gitlink-issue/ # Issue 管理
│ ├── SKILL.md # Issue 操作指南
-│ └── references/ # Issue 命令参考文档
+│ ├── REFERENCE.md # Issue API 参考
+│ └── examples/
+│ └── issue-workflow.md # Issue 全流程工作流
├── gitlink-pr/ # Pull Request
│ ├── SKILL.md # PR 操作指南
-│ └── references/ # PR 命令参考文档
+│ ├── REFERENCE.md # PR API 参考
+│ └── examples/
+│ └── pr-workflow.md # PR 工作流
├── gitlink-member/ # 仓库成员管理
│ └── SKILL.md # 成员与邀请链接操作指南
├── gitlink-branch/ # 分支管理
@@ -85,24 +91,25 @@ skills/
│ └── branch-workflow.md # 分支工作流
├── gitlink-release/ # 版本发布
│ ├── SKILL.md # Release 操作指南
-│ └── references/ # Release 命令参考文档
-├── gitlink-release-auto/ # 自动化 Release 管理
-│ └── SKILL.md # 自动发版、版本号推荐、Release Notes 生成
+│ ├── REFERENCE.md # Release API 参考
+│ └── examples/
+│ └── release-workflow.md # Release 工作流
├── gitlink-search/ # 搜索功能
│ ├── SKILL.md # 搜索操作指南
-│ └── references/ # 搜索命令参考文档
+│ └── examples/
+│ └── search-workflow.md # 搜索工作流
├── gitlink-user/ # 用户管理
-│ ├── SKILL.md # 用户操作指南
-│ └── references/ # 用户命令参考文档
+│ └── SKILL.md # 用户操作指南
├── gitlink-org/ # 组织管理
│ ├── SKILL.md # 组织操作指南
-│ └── references/ # 组织命令参考文档
+│ └── examples/
+│ └── org-workflow.md # 组织工作流
├── gitlink-ci/ # CI/CD
-│ └── SKILL.md # CI 操作指南
+│ ├── SKILL.md # CI 操作指南
+│ └── examples/
+│ └── ci-workflow.md # CI 工作流
├── gitlink-pipeline/ # 流水线工作流
│ └── SKILL.md # Pipeline 操作指南
-├── gitlink-wiki/ # Wiki 页面管理
-│ └── SKILL.md # Wiki 操作指南
├── gitlink-pm/ # 项目管理
│ └── SKILL.md # PM 操作指南
├── gitlink-health/ # 项目健康度分析
@@ -128,7 +135,7 @@ 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 +batch-update`, `issue +batch-delete` |
+| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close` |
| **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` |
@@ -139,11 +146,10 @@ skills/
| Skill | 说明 | 常用命令 |
|-------|------|----------|
| **gitlink-search** | 搜索功能 | `search +repos`, `search +users` |
-| **gitlink-user** | 用户管理 | `user +me`, `user +info`, `user +heatmap`, `user +statistics`, `user +project-trends` |
+| **gitlink-user** | 用户管理 | `user +me`, `user +current`, `user +keys`, `user +activity` |
| **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-wiki** | Wiki 页面管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` |
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
@@ -163,7 +169,7 @@ gitlink-cli repo +info
gitlink-cli repo +info --owner wbtiger --repo gitlink-cli
```
-详见: [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md)
+详见: [gitlink-repo/examples/repo-workflow.md](gitlink-repo/examples/repo-workflow.md)
### 场景 2:创建和管理 Issue
@@ -184,7 +190,7 @@ gitlink-cli issue +close -i 123
gitlink-cli issue +batch-close --numbers 123,124 --dry-run
```
-详见: [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md)
+详见: [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md)
### 场景 3:管理分支和发布
@@ -202,7 +208,7 @@ gitlink-cli release +create -t v1.0.0 -n "v1.0.0 正式版" -b "更新内容..."
gitlink-cli release +view -i
```
-详见: [gitlink-release/SKILL.md](gitlink-release/SKILL.md)
+详见: [gitlink-release/examples/release-workflow.md](gitlink-release/examples/release-workflow.md)
### 场景 4:搜索和发现
@@ -218,7 +224,7 @@ gitlink-cli org +list
gitlink-cli org +info -i Gitlink
```
-详见: [gitlink-search/SKILL.md](gitlink-search/SKILL.md)
+详见: [gitlink-search/examples/search-workflow.md](gitlink-search/examples/search-workflow.md)
---
@@ -227,8 +233,8 @@ gitlink-cli org +info -i Gitlink
### 快速查找
- **我想了解认证**: [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md)
-- **我想查看 API 细节**: [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
-- **我遇到了错误**: [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
+- **我想查看 API 细节**: [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
+- **我遇到了错误**: [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
- **我想看工作流示例**: 查看各 Skill 下的 `examples/` 目录
### 按功能分类
@@ -236,12 +242,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/SKILL.md](gitlink-repo/SKILL.md) - 完整工作流
+- [gitlink-repo/examples/repo-workflow.md](gitlink-repo/examples/repo-workflow.md) - 完整工作流
**Issue 和 PR**:
- [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md) - Issue 命令
- [gitlink-pr/SKILL.md](gitlink-pr/SKILL.md) - PR 命令
-- [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md) - Issue 工作流
+- [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md) - Issue 工作流
**发布和搜索**:
- [gitlink-release/SKILL.md](gitlink-release/SKILL.md) - Release 命令
@@ -283,11 +289,11 @@ gitlink-cli auth login
### Q: 如何查看完整的 API 参考?
-A: 查看 [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
+A: 查看 [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
### Q: 遇到错误怎么办?
-A: 查看 [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
+A: 查看 [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
---
@@ -323,7 +329,7 @@ AI 代理可以:
- 所有边界情况处理正确
- 完整的文档和示例
-详见: [../doc/design.md](../doc/design.md)
+详见: [../doc/SKILLS_TEST_REPORT_2026-04-02.md](../doc/SKILLS_TEST_REPORT_2026-04-02.md)
---
@@ -331,7 +337,8 @@ AI 代理可以:
- [主项目 README](../README.md) - gitlink-cli 项目说明
- [设计文档](../doc/design.md) - 架构设计和开发计划
-- [API 参考文档](../doc/gitlink_api_reference.md) - GitLink API 参考文档
+- [测试报告](../doc/SKILLS_TEST_REPORT_2026-04-02.md) - 功能测试报告
+- [代码同步方案](../doc/CODE_SYNC_STRATEGY_FINAL.md) - GitHub ↔ GitLink 同步设计
- [gitlink-bisync](https://www.gitlink.org.cn/wbtiger/gitlink-bisync) - 代码双向同步系统
---
@@ -339,8 +346,8 @@ AI 代理可以:
## 📞 获取帮助
- **命令帮助**: `gitlink-cli --help`
-- **故障排查**: [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
-- **API 参考**: [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
+- **故障排查**: [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
+- **API 参考**: [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
- **工作流示例**: 查看各 Skill 下的 `examples/` 目录
---
@@ -348,7 +355,7 @@ AI 代理可以:
## 🎓 下一步
1. 阅读 [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md) 了解基础
-2. 查看 [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md) 完成认证
+2. 查看 [gitlink-shared/examples/auth-workflow.md](gitlink-shared/examples/auth-workflow.md) 完成认证
3. 根据需求选择相应的 Skill 文档
4. 参考 `examples/` 目录中的工作流示例
5. 使用 AI 代理自动化你的工作流
diff --git a/skills/gitlink-user/SKILL.md b/skills/gitlink-user/SKILL.md
index 8ab9c71..b405aa2 100644
--- a/skills/gitlink-user/SKILL.md
+++ b/skills/gitlink-user/SKILL.md
@@ -1,7 +1,7 @@
---
name: gitlink-user
version: 1.0.0
-description: "用户操作:查看当前用户、用户详情、贡献热力图、统计和项目趋势。当用户需要查看 GitLink 用户信息时触发。"
+description: "用户操作:查看当前用户、用户详情、Public Keys 和用户统计。当用户需要查看或管理 GitLink 用户信息时触发。"
metadata:
requires:
bins: ["gitlink-cli"]
@@ -21,34 +21,42 @@ metadata:
| Shortcut | 说明 | 需要认证 |
|----------|------|----------|
| `user +me` | 当前登录用户 | 是 |
+| `user +current` | 当前用户详细资料 | 是 |
| `user +info` | 查看用户详情 | 否 |
-| `user +heatmap` | 用户贡献热力图 | 省略 `--user` 时需要 |
-| `user +statistics` | 用户聚合统计 | 省略 `--user` 时需要 |
-| `user +stats` | `user +statistics` 的短别名 | 省略 `--user` 时需要 |
-| `user +project-trends` | 用户项目趋势 | 省略 `--user` 时需要 |
-| `user +trends` | `user +project-trends` 的短别名 | 省略 `--user` 时需要 |
+| `user +keys` | 当前用户 Public Keys 列表 | 是 |
+| `user +key-create` | 创建 Public Key | 是 |
+| `user +key-delete` | 删除 Public Key | 是 |
+| `user +activity` | 用户近期活动统计 | 否 |
+| `user +headmap` | 用户贡献热力图 | 否 |
+| `user +develop` | 用户开发能力统计 | 否 |
+| `user +role` | 用户角色统计 | 否 |
+| `user +major` | 用户专业定位统计 | 否 |
## 使用示例
```bash
# 查看当前用户
gitlink-cli user +me
+gitlink-cli user +current --format json
# 查看其他用户
gitlink-cli user +info --login zhangsan
-# 用户贡献热力图
-gitlink-cli user +heatmap --user zhangsan --year 2026
+# Public Key 管理,写入/删除前先 dry-run
+gitlink-cli user +keys --page 1 --limit 20
+gitlink-cli user +key-create --title "work laptop" --key-file ~/.ssh/id_rsa.pub --dry-run
+gitlink-cli user +key-delete --id 7 --dry-run
# 用户统计
-gitlink-cli user +statistics --user zhangsan --start-time 1704067200 --end-time 1735689600
-
-# 用户项目动态
-gitlink-cli user +project-trends --user zhangsan
+gitlink-cli user +activity --login zhangsan
+gitlink-cli user +headmap --login zhangsan --year 2026
+gitlink-cli user +develop --login zhangsan --start-time 1704067200 --end-time 1735689599
+gitlink-cli user +role --login zhangsan --start-time 1704067200 --end-time 1735689599
+gitlink-cli user +major --login zhangsan --start-time 1704067200 --end-time 1735689599
```
-## 注意事项
+## API 注意事项
-- `user +heatmap`、`user +statistics`、`user +project-trends` 都是只读命令。
-- 省略 `--user` 时会先调用 `user +me` 等价的 `/users/me` 解析当前登录用户,因此需要已登录。
-- `user +stats` 和 `user +trends` 是为贡献者分析工作流保留的短别名。
+- `user +key-create` 支持 `--key` 直接传入公钥内容,也支持 `--key-file` 从本地公钥文件读取。
+- `user +key-create` 和 `user +key-delete` 支持 `--dry-run`,写入/删除前建议先预览请求。
+- 统计命令使用用户 `login`,不是数字用户 ID。