skillhub/docs/06-api-design.md
dongmucat 3a254d7524 fix(auth): guard SUPER_ADMIN role mutations
Signed-off-by: dongmucat <1127093059@qq.com>
2026-07-01 16:23:45 +08:00

549 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# skillhub API 设计
## 0. 标识类型约束
- 所有 API 中出现的用户标识一律为 `string`
- 该约束覆盖路径参数、query 参数、请求体字段、响应 DTO 字段,以及统一响应结构中的业务数据内容。
- 任何旧草案中的整型用户标识写法都已失效,前后端正式契约只允许字符串用户标识。
## 1. 响应结构规范
除文件下载、文件内容读取这类二进制流接口外,所有 JSON API 必须统一使用以下成功响应结构:
```json
{
"code": 0,
"msg": "成功",
"data": {},
"timestamp": "2026-03-12T06:00:00Z",
"requestId": "req-123"
}
```
约束如下:
- `code`:成功时固定为 `0`;失败时固定为 HTTP 状态码,例如 `400``401``403``500`
- `msg`:返回给调用方的用户可读提示文案,必须通过 Spring Boot `MessageSource` + i18n 机制生成,禁止在 controller 中硬编码。
- `msg` 的 locale 必须在响应封装层或全局异常处理层通过 `LocaleContextHolder` 从请求上下文自动获取,禁止在 controller/service 中显式传递 `Locale`
- `data` 承载实际业务数据;列表、分页对象、详情对象、操作结果对象都必须放在 `data` 下。
- 分页响应统一使用 `{ items, total, page, size }`,禁止直接暴露 Spring `Page``content/pageable/sort/first/last` 等内部结构。
- `timestamp`:响应创建时间戳,由后端统一自动生成。
- `requestId`:请求链路 ID由后端统一注入便于日志追踪。
- Controller 层禁止直接返回 `Map`、裸 DTO、裸 `Page`、裸 `List` 作为 JSON 成功响应。
- 普通 JSON 接口应直接返回统一响应 DTO仅文件下载、文件预览等需要自定义状态码或 header 的二进制接口保留 `ResponseEntity`
- 删除、撤销、移动标签等操作也必须返回统一 JSON 结构;如无实体数据,返回 `data.message``data=null`,但外层结构不得变化。
- 错误响应与成功响应使用同一外层结构,不再使用单独的异常 JSON 结构。
- 异常链路中的 `msg` 也必须通过 Spring Boot 标准 i18n 机制生成;参数校验异常、领域异常、认证鉴权异常都必须进入统一的 `@RestControllerAdvice` 出口。
- 二进制流接口保持原始 HTTP 语义,不套 `code/data` 包装:
- `/download`
- `/file`
- 其他返回 `application/octet-stream``application/zip` 等内容类型的接口
成功响应示例:
```json
{
"code": 0,
"msg": "发布成功",
"data": {
"skillId": 123,
"version": "1.0.0"
},
"timestamp": "2026-03-12T06:00:00Z",
"requestId": "req-123"
}
```
错误响应示例:
```json
{
"code": 403,
"msg": "需要命名空间管理员或所有者权限",
"data": null,
"timestamp": "2026-03-12T06:00:00Z",
"requestId": "req-123"
}
```
## 7.1 Public API匿名可访问
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/skills` | 搜索/列表(匿名仅返回 PUBLIC 技能) |
| GET | `/api/v1/skills/{namespace}/{slug}` | 技能详情PUBLIC 匿名可访问) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions` | 版本列表 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}` | 版本详情 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/files` | 文件清单 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/file?path=...` | 读取单个文件query param 避免路径中 / 的解析问题) |
| GET | `/api/v1/skills/{namespace}/{slug}/download` | 下载默认安装版本(最新已发布版本) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/download` | 下载指定版本包 |
| GET | `/api/v1/skills/{namespace}/{slug}/resolve` | 解析技能版本(支持 query param: `version``tag``hash` |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/download` | 按标签下载(解析标签指向的版本后下载) |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/files` | 按标签查看文件清单 |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/file?path=...` | 按标签读取单个文件 |
| GET | `/api/v1/namespaces` | 公开命名空间列表 |
| GET | `/api/v1/namespaces/{slug}` | 命名空间详情 |
Public API 的可见性规则:
- `PUBLIC` 技能:若存在已发布版本,则已登录用户可访问;匿名访问仍受下载/resolve 端点的 namespace 类型限制
- `NAMESPACE_ONLY` 技能:仅该命名空间成员可访问(需登录)
- `PRIVATE` 技能owner 本人 + 该 namespace 的 ADMIN 以上可访问(需登录)
-`latest_version_id = null`,即使 `visibility=PUBLIC`skill 也不会对外公开,只有 owner 可访问
- `hidden=true` 时,普通访客不可访问;仅 owner 或该 namespace 的 `ADMIN` / `OWNER` 可访问
`GET /api/v1/skills/{namespace}/{slug}/versions/{version}``data` 字段除版本基础信息外,还必须包含:
- `parsedMetadataJson``SKILL.md` frontmatter 的完整 JSON 序列化结果
- `manifestJson`:版本文件清单摘要 JSON
## 7.2 Auth API登录与会话相关
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/oauth2/authorization/github` | 发起 GitHub OAuth 登录Spring Security 内置) |
| GET | `/login/oauth2/code/github` | GitHub OAuth 回调Spring Security 内置) |
| GET | `/api/v1/auth/me` | 当前用户信息(未登录返回 401 |
| POST | `/api/v1/auth/logout` | 登出(清除 Session |
| GET | `/api/v1/auth/providers` | 可用的 OAuth Provider 列表(前端渲染登录按钮用) |
| GET | `/api/v1/auth/methods` | 统一登录方式目录(密码/OAuth/direct/bootstrap 元数据) |
| POST | `/api/v1/auth/direct/login` | 显式走直连认证 provider 的兼容登录入口(默认关闭) |
| POST | `/api/v1/auth/session/bootstrap` | 显式尝试用外部被动会话换取 skillhub Session默认关闭 |
`/api/v1/auth/providers` 响应示例:
```json
{
"code": 0,
"msg": "获取成功",
"data": [
{ "id": "github", "name": "GitHub", "authorizationUrl": "/oauth2/authorization/github" }
],
"timestamp": "2026-03-12T06:00:00Z",
"requestId": "req-123"
}
```
前端根据此接口动态渲染登录按钮,新增 Provider 无需改前端代码。
`/api/v1/auth/methods` 返回统一登录方式目录。典型项包括:
- `PASSWORD`:现有本地账号密码登录
- `OAUTH_REDIRECT`OAuth 跳转登录
- `DIRECT_PASSWORD`:默认关闭的直连认证兼容入口
- `SESSION_BOOTSTRAP`:默认关闭的被动会话引导入口
示例:
```json
{
"code": 0,
"msg": "获取成功",
"data": [
{
"id": "local-password",
"methodType": "PASSWORD",
"provider": "local",
"displayName": "Local Account",
"actionUrl": "/api/v1/auth/local/login"
},
{
"id": "oauth-github",
"methodType": "OAUTH_REDIRECT",
"provider": "github",
"displayName": "GitHub",
"actionUrl": "/oauth2/authorization/github"
}
],
"timestamp": "2026-03-12T06:00:00Z",
"requestId": "req-123"
}
```
`/api/v1/auth/session/bootstrap` 请求示例:
```json
{
"provider": "private-sso"
}
```
`/api/v1/auth/session/bootstrap` 协议约束:
- 开源版默认关闭,需显式开启 `skillhub.auth.session-bootstrap.enabled=true`
- 关闭时返回 `403`
- provider 不存在时返回 `400`
- 外部会话不存在或校验失败时返回 `401`
- 成功时返回与 `/api/v1/auth/me` 相同的用户结构,并建立标准 Session
`/api/v1/auth/direct/login` 请求示例:
```json
{
"provider": "private-sso",
"username": "alice",
"password": "secret"
}
```
`/api/v1/auth/direct/login` 协议约束:
- 开源版默认关闭,需显式开启 `skillhub.auth.direct.enabled=true`
- 关闭时返回 `403`
- provider 不存在时返回 `400`
- 成功时返回与 `/api/v1/auth/me` 相同的用户结构,并建立标准 Session
- `/api/v1/auth/local/login` 继续保留,作为现有本地账号入口
## 7.3 Authenticated API需登录
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/star` | 收藏 |
| DELETE | `/api/v1/skills/{namespace}/{slug}/star` | 取消收藏 |
| POST | `/api/v1/skills/{namespace}/{slug}/rating` | 评分 |
| GET | `/api/v1/me/stars` | 我的收藏列表 |
| GET | `/api/v1/me/skills` | 我发布的技能列表 |
### 草稿与审核提交Phase 3 引入)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/withdraw-review` | 撤回提审PENDING_REVIEW → DRAFT同时删除关联的 PENDING review_task |
| POST | `/api/v1/reviews` | 提交指定 `skillVersionId` 进入审核队列 |
### 标签管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/skills/{namespace}/{slug}/tags` | 列出标签 |
| PUT | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}` | 创建/移动自定义标签(`latest` 为系统保留标签,不可通过此接口操作) |
| DELETE | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}` | 删除自定义标签(`latest` 不可删) |
### 技能生命周期管理
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/archive` | 归档技能namespace ADMIN 或 owner |
| POST | `/api/v1/skills/{namespace}/{slug}/unarchive` | 恢复归档namespace ADMIN 或 owner |
| DELETE | `/api/v1/skills/{namespace}/{slug}/versions/{version}` | 删除 DRAFT/REJECTED 版本 |
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/rerelease` | 从已发布版本重新发出一个新版本namespace ADMIN 或 owner |
当前代码中的 skill 生命周期读模型不再依赖 `latestVersionStatus` / `viewingVersionStatus` 一类拼装字段,而统一使用以下 projection
- `headlineVersion`:当前页面应展示的主版本
- `publishedVersion`:当前最新可分发的已发布版本
- `ownerPreviewVersion`owner / namespace 管理者可见的 `PENDING_REVIEW` 预览版本
- `resolutionMode``PUBLISHED` / `OWNER_PREVIEW` / `NONE`
其中:
- 公开详情、公开安装、公开搜索一律只认 `publishedVersion`
- owner 详情页在没有可展示发布版本时,才允许 `headlineVersion` 落到 `ownerPreviewVersion`
- 推广到全局一律使用 `publishedVersion.id`
- `hidden` 是独立治理覆盖层,不属于生命周期状态机
- 常规版本详情接口只放行 `PUBLISHED`,以及 owner 对自己 `PENDING_REVIEW` 版本的预览;`DRAFT / REJECTED / YANKED` 不通过该接口暴露
发布成功响应中的 `data` 至少包含以下字段:
- `skillId`
- `namespace`
- `slug`
- `version`
- `status`
- `fileCount`
- `totalSize`
发布状态约束:
- 普通用户发布成功后,`status``PENDING_REVIEW`
- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/publish``/api/v1/publish` 发布时,`status``PUBLISHED`,且不要求其必须是目标 namespace 成员
- 当前版本保持该审核策略,不再提供“全员直发”的运行模式
- 撤回审核不会删除版本记录,而是 `PENDING_REVIEW → DRAFT`
## 7.4 Token API需登录
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/tokens` | 创建 API Token |
| GET | `/api/v1/tokens` | 列出我的 Token |
| DELETE | `/api/v1/tokens/{id}` | 吊销 Token |
## 7.5 CLI APIBearer Token 认证CLI 主流程由 Device Flow 获取凭证)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/whoami` | 当前 Bearer Token 对应的用户信息 |
| POST | `/api/v1/publish` | 发布技能包(普通用户进入审核;`SUPER_ADMIN` 始终直发) |
| GET | `/api/v1/resolve/{namespace}/{slug}` | 解析版本 |
| GET | `/api/v1/check/{namespace}/{slug}/{version}` | 本地哈希与远端比对 |
### ClawHub CLI 协议兼容层
一期不仅提供 skillhub 自有 CLI API还必须暴露一组兼容 ClawHub CLI 的 registry API。
- 目标:让现有 ClawHub CLI 可通过配置 registry base URL 直接对接 skillhub
- 范围:一期聚焦覆盖 ClawHub CLI 所依赖的核心接口查询、版本解析、下载、发布、whoami
- 要求:兼容层优先保持 ClawHub CLI 既有请求/响应语义;若内部领域模型不同,通过 adapter 层完成协议转换,而不是要求客户端适配 skillhub 私有协议
- 要求:兼容层纳入 OpenAPI 或独立兼容协议文档,并作为正式对外契约维护
- 要求:兼容层与 skillhub 自有 `/api/v1/**` 并存,二者共享同一套权限、审计、限流与领域服务
- 非目标:前端页面不直接依赖兼容层;兼容层用于服务已有 ClawHub CLI 和相关自动化脚本
兼容层最少需要覆盖的能力类别:
- Registry metadata技能查询、技能详情、版本列表、标签/默认版本解析
- Artifact resolution按技能坐标或版本解析下载地址/下载流
- Publish workflow包上传与发布结果返回
- Integrity check版本存在性校验、摘要/哈希比对、whoami/token 上下文确认
如 ClawHub CLI 的现有协议与 skillhub 自有接口存在差异,文档以“兼容 ClawHub CLI 协议”为准skillhub 内部 API 可继续保持当前风格。
## 7.6 Admin API需对应平台角色
Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN
### 平台治理接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/promotions` | 待审核提升申请列表(需 `SKILL_ADMIN` / `SUPER_ADMIN`;路由不在 `/admin/*` 下) |
| GET | `/api/v1/promotions/{id}` | 提升申请详情(提交人本人或 `SKILL_ADMIN` / `SUPER_ADMIN` 可读) |
| POST | `/api/v1/promotions/{id}/approve` | 通过提升申请(需 `SKILL_ADMIN` / `SUPER_ADMIN` |
| POST | `/api/v1/promotions/{id}/reject` | 拒绝提升申请(需 `SKILL_ADMIN` / `SUPER_ADMIN` |
| POST | `/api/v1/admin/skills/{id}/hide` | 隐藏技能(仅 `SUPER_ADMIN` |
| POST | `/api/v1/admin/skills/{id}/unhide` | 恢复技能(仅 `SUPER_ADMIN` |
| POST | `/api/v1/admin/skills/versions/{versionId}/yank` | 撤回已发布版本(`SKILL_ADMIN` / `SUPER_ADMIN` |
### 用户治理(需 USER_ADMIN / SUPER_ADMIN
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/admin/users` | 用户列表 |
| GET | `/api/v1/admin/users/{id}` | 用户详情 |
| PUT | `/api/v1/admin/users/{id}/role` | 修改用户角色USER_ADMIN 不可分配 SUPER_ADMIN也不可修改已有 SUPER_ADMIN 的角色状态) |
| POST | `/api/v1/admin/users/{id}/approve` | 审批待准入用户 |
| POST | `/api/v1/admin/users/{id}/disable` | 封禁用户 |
| POST | `/api/v1/admin/users/{id}/enable` | 解封用户 |
### 审计(需 AUDITOR / SUPER_ADMIN
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/admin/audit-logs` | 审计日志查询 |
## 7.7 Namespace 管理 API需命名空间 OWNER 或 ADMIN
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/namespaces` | 创建命名空间 |
| PUT | `/api/v1/namespaces/{slug}` | 更新命名空间信息 |
| GET | `/api/v1/namespaces/{slug}/members` | 成员列表 |
| POST | `/api/v1/namespaces/{slug}/members` | 添加成员 |
| PUT | `/api/v1/namespaces/{slug}/members/{userId}/role` | 修改成员角色 |
| DELETE | `/api/v1/namespaces/{slug}/members/{userId}` | 移除成员 |
| GET | `/api/v1/reviews?namespaceId={id}` | 该空间待审核列表 |
| POST | `/api/v1/reviews/{id}/approve` | 空间管理员审核通过 |
| POST | `/api/v1/reviews/{id}/reject` | 空间管理员审核拒绝 |
| POST | `/api/v1/promotions` | 申请提升到全局 |
## 7.8 `latest` 语义说明
`latest` 自动跟随最新已发布版本,不可手动移动。
- `skill.latest_version_id`:每次审核通过自动更新,始终指向最新 PUBLISHED 版本
- `yank` 当前最新已发布版本时,需要同步重算 `latest_version_id` 指向下一个最新的 `PUBLISHED` 版本;若不存在则允许为 `null`
- `latest` 标签:系统保留,只读,自动与 `latest_version_id` 同步
- 自定义标签(如 `beta``stable-2026q1`):允许人工创建和移动,用于固定安装通道
| 场景 | 使用字段 | 说明 |
|------|---------|------|
| 搜索索引内容 | `publishedVersion` / `latest_version_id` | 外部协议仍叫 latest但内部语义必须等价于最新已发布版本 |
| `/download`(不带版本号) | `publishedVersion` / `latest_version_id` | 下载最新已发布版本 |
| CLI `install @team/skill` | `publishedVersion` / `latest_version_id` | 等同于 `@latest` |
| CLI `install @team/skill@beta` | `skill_tag` 查询 | 自定义标签指向的版本 |
## 7.9 Resolve 接口说明
`GET /api/v1/skills/{namespace}/{slug}/resolve` 用于解析技能版本,支持以下 query param
| 参数 | 类型 | 说明 |
|------|------|------|
| `version` | string | 精确版本号(如 `1.2.0` |
| `tag` | string | 标签名(如 `beta``latest` |
| `hash` | string | fingerprint 哈希,用于判断本地版本是否与 registry 同步 |
解析优先级:
1. `version``tag` 不可同时传,同时传返回 `400 Bad Request`
2. 仅传 `version`:精确匹配版本号
3. 仅传 `tag`:查询 `skill_tag` 表获取 `target_version_id`
4. 仅传 `hash`:遍历已发布版本,比对 fingerprint
5. 均不传:返回最新已发布版本;实现上可由 `latest_version_id` 或等价 published projection 解析
响应:
```json
{
"code": 0,
"msg": "获取成功",
"data": {
"skillId": 456,
"namespace": "team-name",
"slug": "my-skill",
"version": "1.2.0",
"versionId": 123,
"fingerprint": "sha256:abc123...",
"downloadUrl": "/api/v1/skills/team-name/my-skill/versions/1.2.0/download"
},
"timestamp": "2026-03-12T06:00:00Z",
"requestId": "req-123"
}
```
`hash` 匹配时额外返回 `"matched": true`,不匹配时返回最新版本信息 + `"matched": false`
## 7.10 ClawHub CLI 兼容层 API
兼容层 API 基地址为 `/api/v1`,通过 `/.well-known/clawhub.json` 发现。兼容层使用 canonical slug双连字符映射规则详见 `00-product-direction.md` 1.1 节)。
认证方式:`Authorization: Bearer <token>`。Bearer Token 可来自 CLI Device Flow 或平台 API Token。
### Well-known 发现
```
GET /.well-known/clawhub.json
响应:
{
"apiBase": "/api/v1"
}
```
### 兼容层端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/whoami` | 当前用户信息 |
| GET | `/api/v1/search` | 搜索技能 |
| GET | `/api/v1/resolve` | 通过 slug + version 解析版本 |
| GET | `/api/v1/download/{slug}/{version}` | 下载技能 zip 包 |
| POST | `/api/v1/publish` | 发布技能multipart/form-data`SUPER_ADMIN` 直发) |
### 兼容层请求/响应格式
**GET `/api/v1/whoami`**
```json
{
"handle": "username",
"displayName": "User Name",
"role": "user"
}
```
**GET `/api/v1/search?q={keyword}&page={page}&limit={limit}`**
```json
{
"results": [
{
"slug": "my-skill",
"name": "My Skill",
"description": "...",
"author": { "handle": "username", "displayName": "User Name" },
"version": "1.2.0",
"downloadCount": 100,
"starCount": 50,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-03-01T00:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
```
注意:兼容层返回的 `slug` 为 canonical slug 格式(全局空间直接返回 skill slug团队空间返回 `namespace--skill`)。
**GET `/api/v1/resolve?slug={slug}&version={version}`**
```json
{
"slug": "my-skill",
"version": "1.2.0",
"downloadUrl": "/api/v1/download/my-skill/1.2.0"
}
```
**GET `/api/v1/download/{slug}/{version}`**
返回指定版本的 zip 文件流。默认版本解析由 `resolve` 接口负责。
**POST `/api/v1/publish`**
```
Content-Type: multipart/form-data
Parts:
- file: zip 包
```
一期同步响应,返回发布结果:
```json
{
"slug": "my-skill",
"version": "1.0.0",
"status": "published"
}
```
### 兼容层适配说明
- 兼容层是独立的 Controller 层,内部调用与 native API 相同的领域服务
- 请求进入时将 canonical slug 转换为 `(namespace_id, skill_slug)` 坐标
- 响应返回时将内部坐标转换为 canonical slug
- 兼容层不暴露 namespace 概念,对 ClawHub CLI 透明
- 发布时如果 canonical slug 包含 `--`,解析为团队空间发布;否则发布到全局空间
- 兼容层的认证复用 skillhub Bearer Token 体系ClawHub CLI 通过登录或配置 token 后即可使用
## 7.11 Rate Limiting
分两阶段实施:
### Phase 1Ingress 层基础限流
通过 Nginx Ingress `limit-req` 按 IP 全局限流,覆盖认证、搜索、下载等匿名可访问接口,防止基本的滥用和爬虫。
### Phase 2应用层精细限流
基于 Redis 滑动窗口,按用户/端点分类的精细限流。
| 端点类别 | 限流策略 |
|---------|---------|
| 搜索 API | 已登录 60 次/分钟,匿名 20 次/分钟(按 IP |
| 下载 API | 已登录 120 次/分钟,匿名 30 次/分钟(按 IP |
| 发布 API | 10 次/小时(按用户) |
| 认证 API | 30 次/分钟(按 IP |
触发限流时返回 `429 Too Many Requests` + `Retry-After` Header。
## 7.12 API 设计原则
### Native API`/api/v1/*`
- 统一响应包裹:`{ code, message, data, timestamp }`
- 分页格式:`{ items, total, page, size }`
- 错误码体系:业务错误码 + HTTP 状态码配合
- 版本策略URL path 版本 `/api/v1/`
- 幂等性:写操作通过 `X-Request-Id` + Redis 去重TTL 24h
### Compatibility API`/api/v1/*`
- 响应格式完全遵循 ClawHub 协议,不套统一响应包裹
- 错误响应遵循 ClawHub 格式:`{ error: string, message: string }`
- 分页格式遵循 ClawHub 格式:`{ results, total, page, limit }`
### OpenAPI 文档分离
生成两份独立的 OpenAPI spec
- `openapi-native.json`skillhub Native API用于前端 SDK 生成
- `openapi-compat-clawhub.json`ClawHub 兼容层 API用于兼容性测试和文档