Skill: spzwin/cms-create-skill (version 1.21.3)

--- name: cms-create-skill description: 用于"创建新 Skill / 新建 Skill / 写一个 Skill / 按 XGJK 协议生成 Skill 包 / 浏览查看平台已有 Skill / 搜索 Skill"。多轮引导从零生成符合协议的 Skill 目录与 SKILL.md。仅负责发现与创建——发布上架请用 cms-push-skill，问题反馈请用 cms-report-issue skillcode: cms-create-skill dependencies: - cms-auth-skills --- # CMS Skill 创建工具 **当前版本**: v1.21.3 > **身份声明**：`cms-create-skill` 现在只负责两件事：发现已有 Skill、按协议创建新 Skill。发布、更新、下架统一转交 `cms-push-skill`；问题反馈统一转交 `cms-report-issue`。 ## 两段能力 | # | 核心能力 | 说明 | 需要登录 | |---|---|---|---| | 1 | 发现 Skill | 浏览平台已有 Skill、查看详情、搜索筛选 | 否 | | 2 | 创建 Skill | 按 XGJK 协议模板，多轮对话引导从零构建完整 Skill 包 | 否 | ## 命名区分 - 发现 Skill：先看平台已有的 Skill，避免重复创建。 - 创建 Skill：按协议从零生成内部 Skill 包。 ## 协作边界 - 如果用户要发布到我们内部平台、更新、下架、同步 Skill，不在本 Skill 内执行，统一转交 `cms-push-skill`。 - 如果用户要提交问题、查看问题列表、关闭问题，不在本 Skill 内执行，统一转交 `cms-report-issue`。 - 本 Skill 只负责把 Skill 创建好，并明确下一步应该交给哪个依赖 Skill。 ## 内部 / 外部定义 ### 内部 Skill - 定义：按照 `docs/` 下编号协议文档生成的 Skill 包。 - 典型动作：创建、结构校对、交付给发布工具。 - 典型表达： - "帮我按协议创建一个 Skill" - "这个 Skill 按内部规范生成" ### 外部 Skill - 定义：不是按我方协议原生生成、但已存在于 ClawHub 的 Skill。 - 典型动作：不由本 Skill 创建；如果要同步到平台，转交 `cms-push-skill`。 - 典型表达： - "我在 ClawHub 找到一个 Skill，帮我同步到我们平台" - "把外部 skill 推到我们的平台" ## 能力宪章 ### 核心原则 1. 禁止问用户 token、登录、鉴权细节；鉴权统一依赖 `cms-auth-skills`。 2. 只有声明需要鉴权的动作才获取 `access-token`。 3. 5 步创建流程必须按顺序执行，不跳步。 4. 每一步都要和用户确认，不自作主张。 5. 生成的 Skill 必须严格遵循 `docs/` 下编号协议文档。 6. 目标 Skill 的 `SKILL.md` YAML 头必须声明 `dependencies: - cms-auth-skills`。 7. 目标 Skill 不再生成本地 auth/common，统一依赖 `cms-auth-skills`。 8. 当用户进入发布链路时，只声明下一步转交 `cms-push-skill`，不在本 Skill 中继续维护重复发布实现。 9. 当用户进入问题反馈链路时，只声明下一步转交 `cms-report-issue`，不在本 Skill 中继续维护重复问题实现。 10. 本 Skill 自身只维护 Markdown 说明和创建相关 Python 脚本，不再维护旧接口文档目录。 ### 鉴权规则 - 需要鉴权的动作：无 - 不需要鉴权的动作：`get_skills.py`、5 步创建流程、`fetch_api_doc.py` - 鉴权准备方式：统一通过 `cms-auth-skills` 获取 `access-token` ### 授权依赖 - 创建阶段默认不做写操作，但生成的目标 Skill 仍需依赖 `cms-auth-skills`。 - 当后续进入发布或问题关闭阶段，由对应依赖 Skill 自己处理鉴权动作。 ## 能力概览 ### 发现 Skill | 能力 | 模块 | 说明 | 需要登录 | |---|---|---|---| | 打开技能管理平台 | 浏览器 | 打开 https://skills.mediportal.com.cn | 否 | | 浏览 Skill 列表 | `skill-management` | 查看平台所有已发布 Skill | 否 | | 搜索 Skill | `skill-management` | 按关键词搜索 Skill | 否 | | 查看 Skill 详情 | `skill-management` | 查看某个 Skill 的完整信息 | 否 | ### 创建 Skill | 能力 | 模块 | 说明 | 需要登录 | |---|---|---|---| | 按协议构建 Skill 包 | 5 步流程 | 从零生成完整 Skill 包 | 否 | | 获取接口文档 | 工具脚本 | 拉取并解析 Swagger / Markdown 接口定义 | 否 | ### 协作依赖 | 能力 | 模块 | 说明 | 需要登录 | |---|---|---|---| | 发布 / 更新 / 下架 | `cms-push-skill` | Skill 创建完成后，统一交给发布 Skill | 由依赖 Skill 决定 | | 提交 / 查看 / 关闭问题 | `cms-report-issue` | Skill 运行中问题统一交给问题 Skill | 由依赖 Skill 决定 | ## 意图路由表 ### 发现 Skill | 用户说 | 路由到 | 打开文档 | 执行脚本 | 需要登录 | |---|---|---|---|---| | "打开技能管理"/"打开玄关 Skill"/"Skill 管理页面" | 浏览器打开 | — | `open https://skills.mediportal.com.cn` 或返回链接 | 否 | | "有哪些 Skill"/"查看 Skill 列表"/"看看都有什么" | `skill-management` | `references/skill-management/README.md` | `scripts/skill-management/get_skills.py` | 否 | | "搜索 xxx Skill"/"找一下 xxx 相关的" | `skill-management` | `references/skill-management/README.md` | `scripts/skill-management/get_skills.py --search xxx` | 否 | | "xxx 这个 Skill 怎么样"/"看看 xxx 的详情" | `skill-management` | `references/skill-management/README.md` | `scripts/skill-management/get_skills.py --detail xxx` | 否 | ### 创建 Skill | 用户说 | 路由到 | 打开文档 | 执行脚本 | 需要登录 | |---|---|---|---|---| | "构建 Skill 包"/"按模板创建 Skill" | 5 步流程 | `docs/007_XGJK_SKILL_CREATION_WORKFLOW.md` | `scripts/fetch_api_doc.py` | 否 | | "按我们的协议新建一个 Skill"/"从零生成内部 Skill" | 5 步流程 | `docs/007_XGJK_SKILL_CREATION_WORKFLOW.md` | `scripts/fetch_api_doc.py` | 否 | | "获取接口文档"/"拉取 API 定义" | 工具脚本 | — | `scripts/fetch_api_doc.py` | 否 | ### 发布 / 问题协作 | 用户说 | 路由到 | 打开文档 | 执行脚本 | 需要登录 | |---|---|---|---|---| | "打包并发布"/"帮我发布这个 Skill"/"发布到我们内部平台" | `cms-push-skill` | `references/skill-management/README.md` | `python3 cms-push-skill/scripts/skill-management/publish_skill.py ...` | 由依赖 Skill 决定 | | "把 ClawHub 上这个 Skill 同步到我们平台" | `cms-push-skill` | `references/skill-management/README.md` | `python3 cms-push-skill/scripts/skill-management/publish_skill.py --external ...` | 由依赖 Skill 决定 | | "帮我提交问题"/"上报这个问题" | `cms-report-issue` | `references/issue-report/README.md` | `python3 cms-report-issue/scripts/issue_report/report_issue.py ...` | 由依赖 Skill 决定 | | "看看这个 Skill 有哪些问题"/"问题列表" | `cms-report-issue` | `references/issue-report/README.md` | `python3 cms-report-issue/scripts/issue_report/list_issues.py --skill-code xxx` | 由依赖 Skill 决定 | ## 工作流 A：发现 Skill 发现是起点，先看看平台上有什么，再决定是否创建新的。 ```bash # 浏览全部 Skill python3 cms-create-skill/scripts/skill-management/get_skills.py # 按关键词搜索 python3 cms-create-skill/scripts/skill-management/get_skills.py --search "机器人" # 查看详情 python3 cms-create-skill/scripts/skill-management/get_skills.py --detail "im-robot" ``` ## 工作流 B：创建 Skill（5 步流程） > 完整操作手册：`docs/007_XGJK_SKILL_CREATION_WORKFLOW.md` > 创建规范：`docs/` 下编号协议文档（核心总则见 `001`，详细规则见 `002`-`008`） > 验证清单：`docs/008_XGJK_SKILL_VALIDATION_CHECKLIST.md` ### 协议文档编号说明 `cms-create-skill/docs/` 下的协议文档统一使用三位编号，编号既是排序规则，也是推荐阅读顺序： 1. `001_XGJK_SKILL_PROTOCOL.md` 2. `002_XGJK_SKILL_NAMING_AND_DESCRIPTION_SPEC.md` 3. `003_XGJK_SKILL_STRUCTURE_SPEC.md` 4. `004_XGJK_SKILL_INDEX_WRITING_SPEC.md` 5. `005_XGJK_SKILL_PYTHON_SCRIPT_SPEC.md` 6. `006_XGJK_SKILL_AUTH_AND_SECURITY_SPEC.md` 7. `007_XGJK_SKILL_CREATION_WORKFLOW.md` 8. `008_XGJK_SKILL_VALIDATION_CHECKLIST.md` 命名规则统一为：`NNN_XGJK_SKILL_<TOPIC>.md` ```text Step 1 意图理解与需求确认 Step 2 按协议逐步生成 Step 3 三轮反思检查 Step 4 最终确认 Step 5 完成输出总结 ``` ## 工作流 C：发布协作 这里说的“发布到我们内部平台”，就是把 Skill 注册到当前技能管理平台。 当创建完成后，如果用户明确要发布或同步，不在本 Skill 内继续执行，直接转交 `cms-push-skill`： ```bash # 先安装发布 Skill npx clawhub@latest install cms-push-skill --force ``` 然后按场景进入对应命令： ```bash # 内部 Skill：首次发布 python3 cms-push-skill/scripts/skill-management/publish_skill.py \ ./im-robot --code im-robot --name "IM 机器人" --internal # 内部 Skill：更新 python3 cms-push-skill/scripts/skill-management/publish_skill.py \ ./im-robot --code im-robot --update --version 1.0.0 --internal # 外部 Skill：同步到平台 python3 cms-push-skill/scripts/skill-management/publish_skill.py \ ./im-robot --code im-robot --name "IM 机器人" --external ``` 如果你当前就在 `cms-create-skill` 里，不要继续找本 Skill 自己的发布脚本，下一步直接切到 `cms-push-skill`。 ## 工作流 D：问题反馈协作 如果用户要提交问题或处理问题，统一转交 `cms-report-issue`： ```bash # 先安装问题反馈 Skill npx clawhub@latest install cms-report-issue --force ``` 然后按场景进入对应命令： ```bash # 提交问题 python3 cms-report-issue/scripts/issue_report/report_issue.py \ --skill-code im-robot --version 1.0.0 --error "接口超时" # 查看问题 python3 cms-report-issue/scripts/issue_report/list_issues.py --skill-code im-robot # 标记为已解决 python3 cms-report-issue/scripts/issue_report/update_issue.py \ --issue-id abc123 --status resolved --resolution "已修复重试逻辑" # 关闭问题 python3 cms-report-issue/scripts/issue_report/update_issue.py \ --issue-id abc123 --status closed ``` 如果你当前就在 `cms-create-skill` 里，不要继续找本 Skill 自己的问题脚本，下一步直接切到 `cms-report-issue`。 ## 约束 1. 5 步创建流程必须完整执行。 2. 每一步都要多轮确认。 3. 生成结果必须严格遵循协议文档。 4. 所有说明统一写在 Markdown 文档里。 5. 发布和问题反馈只做依赖声明，不在本 Skill 内重复维护实现。 ## 能力树 ```text cms-create-skill/ ├── SKILL.md ├── docs/ │ ├── 001_XGJK_SKILL_PROTOCOL.md │ ├── 002_XGJK_SKILL_NAMING_AND_DESCRIPTION_SPEC.md │ ├── 003_XGJK_SKILL_STRUCTURE_SPEC.md │ ├── 004_XGJK_SKILL_INDEX_WRITING_SPEC.md │ ├── 005_XGJK_SKILL_PYTHON_SCRIPT_SPEC.md │ ├── 006_XGJK_SKILL_AUTH_AND_SECURITY_SPEC.md │ ├── 007_XGJK_SKILL_CREATION_WORKFLOW.md │ └── 008_XGJK_SKILL_VALIDATION_CHECKLIST.md ├── references/ │ ├── issue-report/ │ │ └── README.md │ └── skill-management/ │ └── README.md └── scripts/ ├── fetch_api_doc.py └── skill-management/ ├── README.md └── get_skills.py ``` ## 备注 - 平台“规范协议”页中，“创建 Skills 的规范”分组以 `README.md` 作为索引；`cms-create-skill/docs/` 保留编号协议文档本体。 - 发布能力统一收敛到 `cms-push-skill`，问题反馈能力统一收敛到 `cms-report-issue`。 - 本 Skill 自身的说明目录已经统一为 `docs/` + `references/` + `scripts/`。

path: docs/001_XGJK_SKILL_PROTOCOL.md
content: # XGJK Skill 协议总览 v2.0

本文档只保留总则、最小结构和交付边界；详细要求已拆分为同目录下的专题规范，避免单个协议文件过大、过杂。

## 0. 文档编号与命名规则

本套协议统一使用以下命名规则：

```text
NNN_XGJK_SKILL_<TOPIC>.md
```

说明：

- `NNN` 为三位数字编号，从 `001` 开始
- 编号同时代表推荐阅读顺序
- `XGJK_SKILL_` 为统一前缀
- `<TOPIC>` 为具体主题，使用全大写英文和下划线

## 1. 适用范围

- 面向按我方规范从零创建的新 Skill
- 面向以 `SKILL.md`、`references/`、`scripts/` 为核心的简化结构
- 面向需要统一鉴权、安全约束和交付校验的内部 Skill

## 2. 最小目录结构

一个合规的 Skill 包至少包含以下结构：

```text
<skill-name>/
├── SKILL.md
├── references/
│   └── <module>/
│       └── README.md
└── scripts/
    └── <module>/
        ├── README.md
        └── <action>.py
```

说明：

- `SKILL.md` 是总索引，负责能力边界、路由规则和能力树
- `references/<module>/README.md` 是模块说明，负责触发场景、输入输出和调用约束
- `scripts/<module>/<action>.py` 是唯一执行入口，负责真正完成业务动作
- `scripts/<module>/README.md` 是脚本索引，负责列出脚本、用途、入参与依赖

## 3. 核心强制规则

1. Skill 名称、目录名、`skillcode` 必须稳定且一致，统一使用小写加连字符格式。
2. 每个可执行动作都必须有对应的 Python 脚本，不允许“先留空后补”。
3. `SKILL.md` 只做索引和规则声明，不承载实现细节，不堆完整请求参数。
4. 模块说明写在 `references/`，脚本执行写在 `scripts/`，两者都必须和 `SKILL.md` 的路由表保持一致。
5. 所有鉴权统一依赖 `cms-auth-skills`，目标 Skill 不自行实现登录流程。
6. 运行时日志、缓存、状态不写回 Skill 包内部，统一放到工作区根目录的 `.cms-log/`。
7. 对用户有副作用的新增、修改、删除、发布、同步等动作，执行前必须再次确认。

## 4. 拆分后的专题文档

当前协议拆分为以下专题文档：

1. `002_XGJK_SKILL_NAMING_AND_DESCRIPTION_SPEC.md`
   说明命名规则、描述写法、YAML 基本字段。
2. `003_XGJK_SKILL_STRUCTURE_SPEC.md`
   说明目录结构、模块划分、文件职责和禁止项。
3. `004_XGJK_SKILL_INDEX_WRITING_SPEC.md`
   说明 `SKILL.md` 的标准写法、路由表格式和能力树要求。
4. `005_XGJK_SKILL_PYTHON_SCRIPT_SPEC.md`
   说明 Python 脚本的文件结构、参数约束、重试、输出和日志规则。
5. `006_XGJK_SKILL_AUTH_AND_SECURITY_SPEC.md`
   说明鉴权依赖、安全边界、敏感信息和危险操作处理方式。
6. `007_XGJK_SKILL_CREATION_WORKFLOW.md`
   给出从零创建 Skill 的 5 步流程。
7. `008_XGJK_SKILL_VALIDATION_CHECKLIST.md`
   用于交付前逐项核对。

## 5. 交付顺序

推荐按以下顺序完成交付：

1. 先阅读本总览，明确最小结构和强制规则。
2. 按专题文档分别完成命名、目录、`SKILL.md`、脚本和安全设计。
3. 按 `007_XGJK_SKILL_CREATION_WORKFLOW.md` 逐步生成目标 Skill。
4. 按 `008_XGJK_SKILL_VALIDATION_CHECKLIST.md` 逐项检查，不能只口头说“已通过”。
5. 完成手工 smoke test 后，再进入发布、同步或交付阶段。

## 6. 最终验收标准

满足以下条件，才算协议层面完成：

- 目录结构完整，且没有占位符残留
- `SKILL.md`、模块说明、脚本索引、Python 脚本四者一致
- 鉴权依赖、输出格式、危险操作确认规则清晰
- 脚本可独立执行，且能在命令行复现
- 校验清单逐项勾选完成，并保留检查结论


path: docs/002_XGJK_SKILL_NAMING_AND_DESCRIPTION_SPEC.md
content: # 命名和描述规范

本规范用于统一 Skill 的命名、字段声明和一句话描述方式，避免同类 Skill 在目录、展示名和发布标识上各写各的。

## 1. Skill 命名规则

Skill 的目录名、`name`、`skillcode` 应尽量保持一致，统一遵循以下规则：

1. 只允许使用小写字母、数字和连字符 `-`
2. 推荐使用 `cms-业务域-动作` 风格
3. 不使用空格、下划线、中文和临时缩写
4. 不使用 `demo`、`test`、`new`、`final` 这类不稳定词

推荐示例：

- `cms-im-robot`
- `cms-order-sync`
- `cms-report-export`

不推荐示例：

- `IMRobot`
- `cms_order_sync`
- `new-skill-demo`

## 2. 模块与脚本命名规则

- 模块名使用小写英文名词或名词短语，如 `robot`、`message`、`report`
- 脚本文件名使用清晰动作名，如 `list_robots.py`、`send_message.py`
- 一个脚本只负责一个清晰动作，不把多个无关动作混在一个文件里
- `SKILL.md`、模块说明、脚本索引中的脚本路径必须完全一致

## 3. 描述写法

`description` 只写业务价值，不写实现细节。推荐一句话说清楚：

`为谁做什么，产出什么结果`

推荐写法：

- `管理企业 IM 机器人并发送通知消息`
- `同步订单状态并输出异常订单摘要`
- `汇总日报数据并生成可直接发送的简报`

不推荐写法：

- `一个很强大的 Skill`
- `调用接口并处理返回值`
- `用于测试脚本功能`

## 4. YAML 头必填字段

`SKILL.md` 的 YAML 头至少包含以下字段：

```yaml
---
name: cms-im-robot
description: 管理企业 IM 机器人并发送通知消息
skillcode: cms-im-robot
github: https://github.com/xgjk/xg-skills/tree/main/cms-im-robot
dependencies:
  - cms-auth-skills
---
```

约束说明：

- `name` 与目录名保持一致
- `skillcode` 与发布标识保持一致
- `github` 指向目标 Skill 的仓库路径
- `dependencies` 里保留 `cms-auth-skills`

## 5. 占位符处理

以下占位符只允许出现在协议模板阶段，不允许出现在最终 Skill 包里：

- `<skill-name>`
- `<skill-code>`
- `<module>`
- `<action>`
- `<一句话描述>`

交付前必须全文搜索并清零。


path: docs/003_XGJK_SKILL_STRUCTURE_SPEC.md
content: # 目录结构规范

本规范只定义 Skill 包“长什么样”，不讨论具体实现代码。

## 1. 最小目录骨架

```text
<skill-name>/
├── SKILL.md
├── references/
│   └── <module>/
│       └── README.md
└── scripts/
    └── <module>/
        ├── README.md
        └── <action>.py
```

## 2. 每一层的职责

| 路径 | 职责 | 必须包含什么 |
|---|---|---|
| `SKILL.md` | 总索引 | 能力概览、工作流、路由表、能力树、约束 |
| `references/<module>/README.md` | 模块说明 | 适用场景、输入要求、输出说明、动作列表 |
| `scripts/<module>/README.md` | 脚本索引 | 脚本清单、运行方式、鉴权前置条件、返回说明 |
| `scripts/<module>/<action>.py` | 唯一执行入口 | 可直接运行的 Python 脚本 |

## 3. 模块划分原则

- 一个模块只覆盖一个明确业务域
- 同一模块下的动作应该围绕同一类对象或同一条业务链路
- 如果两个动作的输入、鉴权、输出和使用场景明显不同，应拆成不同脚本
- 如果一个 Skill 只有一个业务域，也仍然保留模块层，避免后续扩展时打散

## 4. 路由与脚本映射原则

- `SKILL.md` 路由表中的每一条能力，都必须指向一个真实存在的脚本
- 模块说明中的动作列表，应覆盖该模块下全部脚本
- 脚本索引中的脚本路径，应与实际目录完全一致
- 不允许存在“文档里提到但脚本不存在”或“脚本存在但没有入口说明”的孤立文件

## 5. 目录中的禁止项

以下内容不应出现在目标 Skill 包中：

- 本地登录脚本
- 本地鉴权说明文件
- 运行过程中生成的日志、缓存、状态文件
- 与当前 Skill 无关的临时目录、测试残留和历史备份文件
- 未替换的模板占位目录

## 6. 运行时文件位置

如脚本需要记录日志、缓存或版本检查结果，统一写入工作区根目录：

```text
<workspace>/
└── .cms-log/
    ├── log/<skillcode>/
    └── state/<skillcode>/
```

运行时文件属于执行实现细节，不写回 Skill 包目录本身。


path: docs/004_XGJK_SKILL_INDEX_WRITING_SPEC.md
content: # SKILL.md 编写规范

`SKILL.md` 是 Skill 的入口文件，负责告诉 AI 和开发者“这个 Skill 能做什么、应该怎么用、去哪里执行”，而不是把所有实现细节堆在一个文件里。

## 1. YAML 头要求

最小 YAML 头如下：

```yaml
---
name: <skill-name>
description: <一句话描述>
skillcode: <skill-code>
github: https://github.com/xgjk/xg-skills/tree/main/<skill-code>
dependencies:
  - cms-auth-skills
---
```

## 2. 必备章节

`SKILL.md` 至少包含以下内容：

1. 当前版本
2. 接口版本
3. 能力概览
4. 统一规范
5. 授权依赖
6. 输入完整性规则
7. 建议工作流
8. 脚本使用规则
9. 路由与加载规则
10. 宪章
11. 模块路由表
12. 能力树

## 3. 内容边界

`SKILL.md` 负责写：

- Skill 的能力范围
- 使用顺序和加载顺序
- 用户意图与模块/脚本的映射关系
- 对外输出边界、危险操作规则、鉴权依赖入口

`SKILL.md` 不负责写：

- 大段请求字段定义
- 每个脚本的完整实现细节
- 运行时日志路径的硬编码说明
- 与当前 Skill 无关的通用平台文档

## 4. 模块路由表格式

推荐使用以下列：

| 用户意图 | 模块 | 能力摘要 | 模块说明 | 脚本 |
|---|---|---|---|---|
| 查看机器人列表 | `robot` | 获取机器人清单与状态 | `./references/robot/README.md` | `./scripts/robot/list_robots.py` |
| 发送机器人消息 | `message` | 发送通知消息 | `./references/message/README.md` | `./scripts/message/send_message.py` |

要求：

- `用户意图` 写用户真实会说的话
- `模块` 与目录名一致
- `模块说明` 指向真实存在的 `README.md`
- `脚本` 指向真实存在的 `.py`

## 5. 能力树格式

推荐在文末保留实际目录树：

```text
<skill-name>/
├── SKILL.md
├── references/
│   ├── robot/README.md
│   └── message/README.md
└── scripts/
    ├── robot/
    │   ├── README.md
    │   └── list_robots.py
    └── message/
        ├── README.md
        └── send_message.py
```

## 6. 最小正文示例

```markdown
# <Skill 名称> — 索引

本文件提供能力边界、路由规则与使用约束。详细说明见 `references/`，实际执行见 `scripts/`。

**当前版本**: 0.1.0
**接口版本**: v1

**能力概览（2 块能力）**：
- `robot`：查看机器人列表、状态和基础信息
- `message`：发送通知消息并返回发送结果

统一规范：
- 鉴权依赖：`cms-auth-skills/SKILL.md`
- 运行日志：`.cms-log/`

授权依赖：
- 需要鉴权时先读取 `cms-auth-skills/SKILL.md`
- 缺失时先安装依赖，再继续执行

建议工作流（简版）：
1. 先读取 `SKILL.md`，确认能力边界和限制
2. 根据用户意图定位模块
3. 读取对应模块说明
4. 补齐必要输入
5. 执行对应脚本

模块路由与能力索引：
| 用户意图 | 模块 | 能力摘要 | 模块说明 | 脚本 |
|---|---|---|---|---|
| 查看机器人列表 | `robot` | 获取机器人清单与状态 | `./references/robot/README.md` | `./scripts/robot/list_robots.py` |
```

## 7. 一致性要求

- `SKILL.md` 的模块清单必须覆盖实际模块目录
- 路由表必须覆盖实际可执行脚本
- 能力树必须能反映实际文件结构
- 不允许残留任何模板占位符


path: docs/005_XGJK_SKILL_PYTHON_SCRIPT_SPEC.md
content: # Python 脚本规范

本规范用于统一 `scripts/` 下 Python 脚本的写法，确保脚本既能被 AI 调用，也能在命令行独立执行。

## 1. 文件与职责

- 所有业务脚本必须使用 `.py` 后缀
- 一个脚本只负责一个明确动作
- 每个脚本都必须能直接在命令行运行
- 每个脚本都必须在模块说明和脚本索引里有明确入口

## 2. 推荐文件结构

推荐结构如下：

```python
"""
简要说明脚本作用、依赖的鉴权前置条件和最小调用示例。
"""

import argparse
import json
import time
import requests

API_URL = 'https://example.com/api'
AUTH_MODE = 'access-token'
TIMEOUT = 60
MAX_RETRIES = 3
RETRY_INTERVAL = 1


def build_headers() -> dict:
    ...


def call_api(payload: dict) -> dict:
    ...


def main() -> None:
    ...


if __name__ == '__main__':
    main()
```

## 3. 命令行要求

- 使用 `argparse` 解析参数
- 参数名与文档中的字段名保持一致
- 缺失必填参数时，应给出明确报错
- 不把复杂业务逻辑塞进命令行参数描述里

## 4. 请求与重试要求

- 使用 `requests` 发起请求
- 显式设置 `timeout`
- 如业务允许重试，间隔至少 1 秒，最多 3 次
- 严禁无限循环重试
- 超过最大次数后必须抛出或输出明确错误
- 如需关闭证书校验或跟随跳转，应在代码中显式写出，而不是依赖默认行为

## 5. 输出要求

- 默认输出结构化 JSON
- 成功输出应优先保留最小必要字段
- 失败输出应包含明确错误原因
- 非调试场景不直接回显整段原始响应
- 输出需兼顾 AI 可读性与命令行可读性

## 6. 鉴权要求

- 鉴权模式必须与模块说明、脚本索引保持一致
- 需要 `appKey` 或 `access-token` 时，统一通过 `cms-auth-skills` 获取
- `nologin` 动作不依赖额外鉴权获取流程
- 业务脚本不负责登录、不负责换取 token

## 7. 日志与状态

- 如脚本需要日志，统一写入 `.cms-log/log/<skillcode>/`
- 如脚本需要缓存或状态，统一写入 `.cms-log/state/<skillcode>/`
- 日志中不得输出 token、appKey、authorization 等敏感值
- 不将运行时状态写回 Skill 包目录

## 8. 交付前自检

每个脚本完成后，至少检查以下事项：

- 脚本文件名是否与路由表一致
- 参数名是否与文档一致
- 鉴权模式是否与文档一致
- 超时、重试、错误退出是否明确
- `main()` 和 `if __name__ == '__main__'` 是否齐全


path: docs/006_XGJK_SKILL_AUTH_AND_SECURITY_SPEC.md
content: # 鉴权与安全规范

本规范只定义一条核心原则：所有需要鉴权的 Skill，统一通过 `cms-auth-skills` 获取并使用具体的 `appKey` 或 `access-token`。

## 1. 统一依赖

所有需要鉴权的 Skill 都统一依赖：

```yaml
dependencies:
  - cms-auth-skills
```

## 2. 统一规则

1. 目标 Skill 不自己实现登录流程。
2. 目标 Skill 不自己维护鉴权方案说明。
3. 目标 Skill 只声明当前动作属于哪一种鉴权模式：`nologin`、`appKey`、`access-token`。
4. 当动作需要 `appKey` 或 `access-token` 时，统一通过 `cms-auth-skills` 获取。

## 3. 禁止事项

- 不在目标 Skill 中重复编写 token、appKey 的获取方式
- 不在目标 Skill 中实现本地登录、换 token、鉴权兜底逻辑

## 4. 交付要求

- 文档中明确写出动作的鉴权模式
- 需要鉴权时，统一指向 `cms-auth-skills`
- 除此之外，不再展开具体鉴权实现细节


path: docs/007_XGJK_SKILL_CREATION_WORKFLOW.md
content: # Skill 包构建流程（5 步详细指南）

> 本文档是按当前简化协议从零构建 Skill 包的完整操作手册。
> 创建规范以同目录编号文档为准：核心总则见 `001_XGJK_SKILL_PROTOCOL.md`，详细规则见 `002`-`008`。

## Step 1：理解业务与确认范围

先把目标说清楚，再开始建包。

至少确认以下问题：

1. 这个 Skill 解决什么问题？
2. 谁会触发它？用户会怎么说？
3. 需要拆成几个模块？
4. 每个模块下有哪些明确动作？
5. 哪些动作是读操作，哪些动作是写操作？
6. 每个动作需要哪种鉴权模式？

这一阶段的交付物：

- Skill 名称
- Skill 描述
- `skillcode`
- 模块清单
- 动作清单
- 各动作的鉴权模式

## Step 2：创建目录骨架

按最小结构先搭目录：

```bash
mkdir -p <skill-name>/references/<module>
mkdir -p <skill-name>/scripts/<module>
```

然后立即确认以下规则：

- 目标 Skill 统一依赖 `cms-auth-skills`
- 目标 Skill 不实现本地登录流程
- 运行时日志与状态写到工作区根目录 `.cms-log/`

这一阶段只做结构，不写实现细节。

## Step 3：先写 SKILL.md

`SKILL.md` 是总索引，应该先确定下来。

必须完成：

1. YAML 基本字段
2. 能力概览
3. 统一规范
4. 授权依赖说明
5. 输入完整性规则
6. 建议工作流
7. 脚本使用规则
8. 路由与加载规则
9. 宪章
10. 模块路由表
11. 能力树

这一阶段的目标不是“把内容写长”，而是把边界、入口和路由写准。

## Step 4：按模块逐个补全说明与脚本

对每个模块按以下顺序处理：

1. 编写 `references/<module>/README.md`
2. 编写 `scripts/<module>/<action>.py`
3. 更新 `scripts/<module>/README.md`
4. 回填 `SKILL.md` 中对应的模块路由表

### 4.1 模块说明至少写清楚

- 模块是干什么的
- 用户会在什么场景下触发
- 需要哪些输入
- 每个动作做什么
- 每个动作使用什么鉴权模式
- 输出结果如何理解

### 4.2 Python 脚本至少做到

- 可以直接命令行运行
- 参数名与文档一致
- 鉴权读取方式明确
- 请求超时明确
- 重试策略明确
- 输出结构明确

### 4.3 每完成一个动作就做一次迷你自检

- 路由表里的脚本路径是否存在
- 模块说明里的动作名是否覆盖该脚本
- 脚本参数与说明是否一致
- 鉴权模式在说明、脚本索引、脚本实现中是否一致

## Step 5：按清单完成三轮复核并交付

### 第一轮：逐项打勾

按 `008_XGJK_SKILL_VALIDATION_CHECKLIST.md` 逐条检查，不能跳项。

### 第二轮：交叉一致性检查

至少核对以下内容：

- `SKILL.md` 路由表和实际脚本是否一致
- 模块说明和脚本索引是否一致
- 实际目录和能力树是否一致
- 文档中的动作名、模块名、路径是否一致

### 第三轮：手工 smoke test

至少挑选一个读操作和一个写操作脚本做人工验证：

- 读操作验证输出格式
- 写操作验证确认流程和错误处理

全部通过后，再输出最终结论：

```text
✅ Skill 构建完成
- Skill 名称：<name>
- 模块数量：M
- 脚本数量：N
- 清单检查：全部通过
- 手工 smoke test：已完成
- 遗留问题：无
```


path: docs/008_XGJK_SKILL_VALIDATION_CHECKLIST.md
content: # Skills 规范校验清单

用于检查一个 Skill 包是否符合当前简化协议。执行时必须逐项核对，不允许只口头说“已通过”。

> 本清单与同目录编号协议文档保持同步：核心总则见 `001_XGJK_SKILL_PROTOCOL.md`，详细规则见 `002`-`007`。如有冲突，以最新专题规范为准。

## A. 基础声明

- [ ] 存在 `SKILL.md`
- [ ] `SKILL.md` 的 YAML 头包含 `name`、`description`、`skillcode`、`github`
- [ ] `SKILL.md` 的 YAML 头包含 `dependencies: - cms-auth-skills`
- [ ] `name`、目录名、`skillcode` 保持一致
- [ ] `name` 和 `skillcode` 只使用小写字母、数字和连字符
- [ ] 全文不存在模板占位符残留（如 `<skill-name>`、`<module>`、`<action>`）

## B. 目录与路由

- [ ] 存在 `references/` 目录
- [ ] 存在 `scripts/` 目录
- [ ] 每个模块都有 `references/<module>/README.md`
- [ ] 每个模块都有 `scripts/<module>/README.md`
- [ ] 每个可执行动作都有对应的 `scripts/<module>/<action>.py`
- [ ] `SKILL.md` 的能力概览与实际模块目录一致
- [ ] `SKILL.md` 的模块路由表与实际脚本一致
- [ ] `SKILL.md` 的能力树与实际目录结构一致
- [ ] 不存在未在 `SKILL.md` 中声明的孤立脚本

## C. 文档完整性

- [ ] `SKILL.md` 包含能力概览
- [ ] `SKILL.md` 包含统一规范
- [ ] `SKILL.md` 包含授权依赖说明
- [ ] `SKILL.md` 包含输入完整性规则
- [ ] `SKILL.md` 包含建议工作流
- [ ] `SKILL.md` 包含脚本使用规则
- [ ] `SKILL.md` 包含路由与加载规则
- [ ] `SKILL.md` 包含宪章
- [ ] 每个模块说明包含适用场景、输入要求、输出说明和动作列表
- [ ] 每个脚本索引包含脚本清单、运行方式、鉴权前置条件和返回说明

## D. Python 脚本

- [ ] 所有业务脚本均为 Python 文件
- [ ] 每个脚本可在命令行独立执行
- [ ] 每个脚本包含 `main()` 函数
- [ ] 每个脚本包含 `if __name__ == '__main__'` 守卫
- [ ] 每个脚本的参数名与文档保持一致
- [ ] 每个脚本显式设置超时
- [ ] 如实现重试，重试间隔不少于 1 秒，最大次数不超过 3 次
- [ ] 不存在无限循环重试逻辑
- [ ] 脚本默认输出结构化 JSON 或明确可解析文本

## E. 鉴权与安全

- [ ] 需要鉴权时，明确依赖 `cms-auth-skills`
- [ ] 每个脚本都声明了明确的鉴权模式：`nologin`、`appKey` 或 `access-token`
- [ ] 需要 `appKey` 或 `access-token` 时，统一通过 `cms-auth-skills` 获取
- [ ] `nologin` 动作不依赖额外鉴权获取流程
- [ ] 目标 Skill 未实现本地登录流程
- [ ] 不向用户输出 token、appKey、cookie、authorization 等敏感值
- [ ] 写操作、发布操作、同步操作在执行前要求再次确认

## F. 输出、日志与运行时状态

- [ ] 对用户只输出最小必要信息
- [ ] 非必要场景不回显完整原始响应
- [ ] 如脚本输出日志，统一写入 `.cms-log/log/<skillcode>/`
- [ ] 如脚本记录状态，统一写入 `.cms-log/state/<skillcode>/`
- [ ] 运行时日志和状态不写回 Skill 包目录
- [ ] 日志中不出现敏感信息

## G. 交付前复核

- [ ] 已按 `007_XGJK_SKILL_CREATION_WORKFLOW.md` 走完生成流程
- [ ] 已逐项勾选本清单，并记录每项结论
- [ ] 已完成至少一次手工 smoke test
- [ ] 已确认文档、脚本、目录三者没有互相矛盾


path: references/issue-report/README.md
content: # issue-report 模块说明

`cms-create-skill` 不再内置问题反馈实现。

如果用户在创建完成后要提交问题、查看问题列表、解决或关闭问题，统一转交 `cms-report-issue`。

## 交接命令

```bash
# 安装问题反馈 Skill
npx clawhub@latest install cms-report-issue --force

# 提交问题
python3 cms-report-issue/scripts/issue_report/report_issue.py \
  --skill-code my-skill --version 1.0.0 --error "..."

# 查看问题
python3 cms-report-issue/scripts/issue_report/list_issues.py --skill-code my-skill

# 标记已解决
python3 cms-report-issue/scripts/issue_report/update_issue.py \
  --issue-id abc123 --status resolved --resolution "已修复"
```

## 说明

- `cms-create-skill` 只保留依赖声明，不再维护 `issue_report` 脚本目录。
- 问题状态更新需要的鉴权规则，由 `cms-report-issue` 自己负责。
- 如果你当前正在 `cms-create-skill` 里，这里就是问题反馈的下一步入口。


path: references/skill-management/README.md
content: # skill-management 模块说明

## 模块职责

`skill-management` 在 `cms-create-skill` 内只负责发现：

1. 发现平台已有 Skill。

## 脚本清单

| 脚本 | 说明 | 需要鉴权 |
|---|---|---|
| `get_skills.py` | 查看列表、搜索、查看详情 | 否 |

## 常见场景

| 场景 | 触发意图 | 脚本 |
|---|---|---|
| 用户要查看平台上有哪些 Skill | "查看 Skill 列表"、"有哪些 Skill" | `get_skills.py` |
| 用户要搜索某类 Skill | "搜一下 xxx 相关 Skill" | `get_skills.py --search xxx` |
| 用户要把 Skill 发布到我们内部平台 | "发布 Skill"、"注册 Skill"、"发布到内部平台" | 转交 `cms-push-skill` |
| 用户要更新或下架 Skill | "更新 Skill"、"下架 Skill" | 转交 `cms-push-skill` |

## 发布移交

这里说的“内部平台”，就是当前技能管理平台。

```bash
# 安装发布 Skill
npx clawhub@latest install cms-push-skill --force

# 查看列表
python3 cms-create-skill/scripts/skill-management/get_skills.py

# 内部 Skill：一站式发布到内部平台
python3 cms-push-skill/scripts/skill-management/publish_skill.py \
  ./im-robot --code im-robot --name "IM 机器人" --internal

# 内部 Skill：更新到内部平台
python3 cms-push-skill/scripts/skill-management/publish_skill.py \
  ./im-robot --code im-robot --update --version 1.0.0 --internal

# 外部 Skill：同步到平台
python3 cms-push-skill/scripts/skill-management/publish_skill.py \
  ./im-robot --code im-robot --name "IM 机器人" --external
```

## 说明

- 本模块不再维护发布、更新、下架的重复脚本实现。
- 发布链路统一由 `cms-push-skill` 负责。
- 本模块不维护旧接口文档副本，所有说明都放在 Markdown 文档里。
- 如果你当前在 `cms-create-skill` 里，这里就是发布到内部平台的下一步入口。


path: scripts/fetch_api_doc.py
content: #!/usr/bin/env python3
"""
API 接口文档获取器（智能识别 URL 类型）

用途：
  根据 URL 自动识别文档类型并获取接口信息：
  - Markdown URL → 直接获取 Markdown 内容
  - Swagger URL (doc.html#/...) → 解析 Swagger JSON，提取目标接口的完整子集

使用方式：
  python3 fetch_api_doc.py <url1> [url2] ... [--output-dir <dir>]

示例：
  # Markdown 文档（自动识别）
  python3 fetch_api_doc.py "https://example.com/api-docs/im-robot.md"

  # Swagger 链接（自动识别，支持多个）
  python3 fetch_api_doc.py \
    "https://host/api-center/doc.html#/im/1.机器人管理/deleteMyRobotUsingPOST" \
    "https://host/api-center/doc.html#/im/1.机器人管理/listVisibleUsingGET" \
    --output-dir ./temp

  # 混合使用
  python3 fetch_api_doc.py "https://example.com/api.md" "https://host/api-center/doc.html#/im/xxx/someAPI"
"""

import sys
import json
import os
import requests
import warnings

# 禁用 InsecureRequestWarning (因为 verify=False)
warnings.filterwarnings("ignore", category=requests.packages.urllib3.exceptions.InsecureRequestWarning)

from urllib.parse import urlparse, unquote


# ============================================================
# 通用：HTTP 请求
# ============================================================

def _http_get(url: str, accept: str = "*/*") -> bytes:
    """通用 HTTP GET，支持 gzip，忽略 SSL 验证"""
    headers = {
        "Accept": accept,
        "User-Agent": "SkillDocFetcher/1.0",
    }

    try:
        response = requests.get(
            url,
            headers=headers,
            verify=False,
            allow_redirects=True,
            timeout=30,
        )
        response.raise_for_status()
        return response.content
    except Exception as e:
        error_msg = response.text if 'response' in locals() else str(e)
        raise RuntimeError(f"请求 {url} 失败: {error_msg}")


# ============================================================
# URL 类型识别
# ============================================================

def detect_url_type(url: str) -> str:
    """
    自动识别 URL 类型：
      - 'swagger' : 包含 doc.html# 的 Swagger 链接
      - 'markdown' : 其他（默认当作 Markdown/文本文档）
    """
    parsed = urlparse(url)
    path_lower = parsed.path.lower()
    fragment = parsed.fragment

    # Swagger 特征：路径包含 doc.html 且有 hash fragment
    if "doc.html" in path_lower and fragment:
        return "swagger"

    return "markdown"


# ============================================================
# Markdown 文档获取
# ============================================================

def fetch_markdown(url: str) -> str:
    """从 URL 获取 Markdown/文本文档内容"""
    data = _http_get(url, accept="text/markdown, text/plain, */*")
    return data.decode("utf-8")


# ============================================================
# Swagger 解析（从原 parse_swagger.py 合并）
# ============================================================

def parse_swagger_url(doc_url: str) -> dict:
    """
    解析 doc.html# 链接，提取 host、serviceName、operationId。

    输入：https://host/api-center/doc.html#/im/2.消息管理/msgListByIdsUsingPOST
    返回：{ host, scheme, service_name, operation_id, swagger_url }
    """
    parsed = urlparse(doc_url)
    host = parsed.hostname
    port = parsed.port
    scheme = parsed.scheme or "https"
    fragment = unquote(parsed.fragment).lstrip("/")
    parts = fragment.split("/")

    if len(parts) < 2:
        raise ValueError(f"URL fragment 格式不正确: {fragment}")

    service_name = parts[0]
    operation_id = parts[-1]

    # 提取 doc.html 之前的路径前缀（如 /api-center）
    path = parsed.path
    doc_idx = path.lower().find("/doc.html")
    path_prefix = path[:doc_idx] if doc_idx >= 0 else ""

    # 构造正确的 origin（保留 scheme、host、port）
    if port and port not in (80, 443):
        origin = f"{scheme}://{host}:{port}"
    else:
        origin = f"{scheme}://{host}"

    swagger_url = f"{origin}{path_prefix}/{service_name}/v2/api-docs"

    return {
        "host": host, "scheme": scheme,
        "service_name": service_name,
        "operation_id": operation_id,
        "swagger_url": swagger_url,
    }


def fetch_swagger_json(swagger_url: str) -> dict:
    """获取 Swagger JSON 文档"""
    data = _http_get(swagger_url, accept="application/json")
    return json.loads(data.decode("utf-8"))


def collect_refs(obj, refs: set):
    """递归收集 $ref 引用的 definition 名称"""
    if isinstance(obj, dict):
        if "$ref" in obj:
            ref_path = obj["$ref"]
            if ref_path.startswith("#/definitions/"):
                refs.add(ref_path.split("/")[-1])
        for v in obj.values():
            collect_refs(v, refs)
    elif isinstance(obj, list):
        for item in obj:
            collect_refs(item, refs)


def resolve_all_definitions(swagger: dict, initial_refs: set) -> dict:
    """递归解析所有嵌套的 definitions"""
    all_defs = swagger.get("definitions", {})
    resolved, queue, visited = {}, list(initial_refs), set()
    while queue:
        name = queue.pop(0)
        if name in visited:
            continue
        visited.add(name)
        if name in all_defs:
            resolved[name] = all_defs[name]
            nested = set()
            collect_refs(all_defs[name], nested)
            queue.extend(r for r in nested if r not in visited)
    return resolved


def extract_api_subset(swagger: dict, operation_id: str) -> dict:
    """提取单个接口的完整 Swagger 子集"""
    for path, methods in swagger.get("paths", {}).items():
        for method, detail in methods.items():
            if isinstance(detail, dict) and detail.get("operationId") == operation_id:
                refs = set()
                collect_refs(detail, refs)
                definitions = resolve_all_definitions(swagger, refs)
                subset = {
                    "swagger": swagger.get("swagger", "2.0"),
                    "info": swagger.get("info", {}),
                    "host": swagger.get("host", ""),
                    "basePath": swagger.get("basePath", "/"),
                    "schemes": swagger.get("schemes", []),
                    "consumes": swagger.get("consumes", ["*/*"]),
                    "produces": swagger.get("produces", ["*/*"]),
                    "paths": {path: {method: detail}},
                }
                if definitions:
                    subset["definitions"] = definitions
                return subset
    raise ValueError(f"operationId not found: {operation_id}")


# ============================================================
# 统一处理入口
# ============================================================

def process_urls(urls: list, output_dir: str = None) -> list:
    """
    处理多个 URL，自动识别类型并分别处理。
    返回处理结果列表。
    """
    results = []
    swagger_cache = {}  # 缓存：同一 swagger_url 只请求一次

    for url in urls:
        url_type = detect_url_type(url)
        print(f"[识别] {url_type.upper()} — {url}", file=sys.stderr)

        try:
            if url_type == "markdown":
                # ── Markdown 文档 ──
                content = fetch_markdown(url)
                print(f"[完成] 获取 {len(content)} 字符", file=sys.stderr)

                result = {
                    "source_url": url,
                    "type": "markdown",
                    "content": content,
                }
                results.append(result)

                # 写入文件
                if output_dir:
                    os.makedirs(output_dir, exist_ok=True)
                    # 从 URL 提取文件名
                    filename = os.path.basename(urlparse(url).path) or "api-doc.md"
                    if not filename.endswith(".md"):
                        filename += ".md"
                    filepath = os.path.join(output_dir, filename)
                    with open(filepath, "w", encoding="utf-8") as f:
                        f.write(content)
                    print(f"[保存] {filepath}", file=sys.stderr)

            elif url_type == "swagger":
                # ── Swagger 链接 ──
                parsed = parse_swagger_url(url)
                swagger_url = parsed["swagger_url"]

                if swagger_url not in swagger_cache:
                    print(f"[获取] {swagger_url} ...", file=sys.stderr)
                    swagger_cache[swagger_url] = fetch_swagger_json(swagger_url)

                swagger = swagger_cache[swagger_url]
                subset = extract_api_subset(swagger, parsed["operation_id"])

                result = {
                    "source_url": url,
                    "type": "swagger",
                    "service_name": parsed["service_name"],
                    "operation_id": parsed["operation_id"],
                    "swagger_subset": subset,
                }
                results.append(result)

                # 写入文件
                if output_dir:
                    os.makedirs(output_dir, exist_ok=True)
                    filename = f"{parsed['service_name']}_{parsed['operation_id']}.json"
                    filepath = os.path.join(output_dir, filename)
                    with open(filepath, "w", encoding="utf-8") as f:
                        json.dump(subset, f, ensure_ascii=False, indent=2)
                    print(f"[完成] {parsed['operation_id']} -> {filepath}", file=sys.stderr)

        except Exception as e:
            print(f"[错误] {url}: {e}", file=sys.stderr)
            results.append({"source_url": url, "error": str(e)})

    return results


# ============================================================
# CLI 入口
# ============================================================

if __name__ == "__main__":
    if len(sys.argv) < 2 or any(arg in ("-h", "--help") for arg in sys.argv[1:]):
        print("用法: python3 fetch_api_doc.py <url1> [url2] ... [--output-dir <dir>]")
        print("")
        print("自动识别 URL 类型：")
        print("  - 包含 doc.html# → Swagger 解析模式")
        print("  - 其他 URL → Markdown 文档获取模式")
        print("  - 支持混合使用")
        print("")
        print("示例:")
        print('  python3 fetch_api_doc.py "https://example.com/api-docs/im.md"')
        print('  python3 fetch_api_doc.py "https://host/api-center/doc.html#/im/xxx/someAPI" --output-dir ./temp')
        sys.exit(0 if len(sys.argv) >= 2 else 1)

    urls = []
    output_dir = None

    i = 1
    while i < len(sys.argv):
        if sys.argv[i] == "--output-dir" and i + 1 < len(sys.argv):
            output_dir = sys.argv[i + 1]
            i += 2
        else:
            urls.append(sys.argv[i])
            i += 1

    if not urls:
        print("错误: 至少提供一个 URL", file=sys.stderr)
        sys.exit(1)

    results = process_urls(urls, output_dir)

    # 输出结果到 stdout
    # 如果只有一个 markdown 结果，直接输出内容（方便 AI 读取）
    if len(results) == 1 and results[0].get("type") == "markdown":
        print(results[0]["content"])
    else:
        print(json.dumps(results, ensure_ascii=False, indent=2))


path: scripts/skill-management/README.md
content: # skill-management 脚本清单

## 模块定位

`skill-management` 在 `cms-create-skill` 中只保留发现能力：

1. 查看平台已有 Skill。

对应说明统一见 `../../references/skill-management/README.md`。

## 脚本列表

| 脚本 | 说明 | 需要鉴权 |
|---|---|---|
| `get_skills.py` | 获取 Skill 列表、搜索、详情 | 否 |

## 发布移交

```bash
# 查看当前已发布的 Skill
python3 scripts/skill-management/get_skills.py

# 一站式发布内部 Skill
python3 cms-push-skill/scripts/skill-management/publish_skill.py \
  ./im-robot --code im-robot --name "IM 机器人" --internal
```

## 说明

- 发布、更新、下架脚本已经从 `cms-create-skill` 中剥离。
- 如需继续发布链路，统一转交 `cms-push-skill`。


path: scripts/skill-management/get_skills.py
content: #!/usr/bin/env python3
"""
发现 Skill — 浏览、搜索、查看详情

完整实现位于 cms-find-skills/scripts/skill_registry/get_skills.py。
本文件作为薄封装，自动定位并复用该实现，避免双份维护。

使用方式：
  python3 cms-create-skill/scripts/skill-management/get_skills.py
  python3 cms-create-skill/scripts/skill-management/get_skills.py --search "机器人"
  python3 cms-create-skill/scripts/skill-management/get_skills.py --detail "im-robot"
"""

from __future__ import annotations

import os
import sys

_HERE = os.path.dirname(os.path.abspath(__file__))
_REPO_ROOT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
_FIND_SKILLS_REGISTRY = os.path.join(
    _REPO_ROOT, "cms-find-skills", "scripts", "skill_registry"
)

if not os.path.isdir(_FIND_SKILLS_REGISTRY):
    print(
        f"找不到 cms-find-skills/scripts/skill_registry: {_FIND_SKILLS_REGISTRY}",
        file=sys.stderr,
    )
    sys.exit(1)

sys.path.insert(0, _FIND_SKILLS_REGISTRY)

from get_skills import main  # type: ignore  # noqa: E402

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)