Bläddra i källkod

更名:对外接口与网关 qiwe 改为 qiwei(工具名/路径/环境变量,旧 QIWE_* 环境变量保留兼容)

gangvy 2 månader sedan
förälder
incheckning
d091f5ed31
30 ändrade filer med 406 tillägg och 398 borttagningar
  1. 4 4
      claude-code/claude-code-qiwe-assistant/.env.example
  2. 1 1
      claude-code/claude-code-qiwe-assistant/.mcp.json
  3. 43 43
      claude-code/claude-code-qiwe-assistant/README.md
  4. 1 1
      claude-code/claude-code-qiwe-assistant/bin/qiwei-official-cli.js
  5. 6 6
      claude-code/claude-code-qiwe-assistant/docs/OUTPUT-STANDARD.md
  6. 10 10
      claude-code/claude-code-qiwe-assistant/docs/specs/qiwei-subscription-flow.md
  7. 3 3
      claude-code/claude-code-qiwe-assistant/mcp/catalog/qiwei-endpoints.json
  8. 1 1
      claude-code/claude-code-qiwe-assistant/mcp/src/core/api-catalog.js
  9. 27 19
      claude-code/claude-code-qiwe-assistant/mcp/src/core/credentials.js
  10. 4 4
      claude-code/claude-code-qiwe-assistant/mcp/src/core/login-flow-server.js
  11. 1 1
      claude-code/claude-code-qiwe-assistant/mcp/src/core/output-paths.js
  12. 6 6
      claude-code/claude-code-qiwe-assistant/mcp/src/core/subscribe-page.js
  13. 6 6
      claude-code/claude-code-qiwe-assistant/mcp/src/core/wecom-cli-runtime.js
  14. 9 9
      claude-code/claude-code-qiwe-assistant/mcp/src/providers/wecom-official-cli.js
  15. 45 45
      claude-code/claude-code-qiwe-assistant/mcp/src/server.js
  16. 34 34
      claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwei-api-catalog-run.js
  17. 58 58
      claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwei-login-run.js
  18. 19 19
      claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwei-subscription-run.js
  19. 8 8
      claude-code/claude-code-qiwe-assistant/mcp/src/tools/wecom-official-cli-run.js
  20. 2 2
      claude-code/claude-code-qiwe-assistant/package-lock.json
  21. 4 4
      claude-code/claude-code-qiwe-assistant/package.json
  22. 7 7
      claude-code/claude-code-qiwe-assistant/scripts/build-catalog.py
  23. 25 25
      claude-code/claude-code-qiwe-assistant/scripts/smoke-test.js
  24. 5 5
      claude-code/claude-code-qiwe-assistant/scripts/wecom-cli-smoke-test.js
  25. 0 42
      claude-code/claude-code-qiwe-assistant/skills/qiwe-login/SKILL.md
  26. 8 8
      claude-code/claude-code-qiwe-assistant/skills/qiwei-api-catalog/SKILL.md
  27. 13 13
      claude-code/claude-code-qiwe-assistant/skills/qiwei-capability-router/SKILL.md
  28. 42 0
      claude-code/claude-code-qiwe-assistant/skills/qiwei-login/SKILL.md
  29. 7 7
      claude-code/claude-code-qiwe-assistant/skills/qiwei-official-doc/SKILL.md
  30. 7 7
      claude-code/claude-code-qiwe-assistant/skills/qiwei-official-meeting/SKILL.md

+ 4 - 4
claude-code/claude-code-qiwe-assistant/.env.example

@@ -1,17 +1,17 @@
 # Fmode 网关转发的企业微信接口
-QIWE_API_BASE=https://server.fmode.cn/api/qiwe
+QIWEI_API_BASE=https://server.fmode.cn/api/qiwei
 
 # 可选:稳定标识一个企微设备;留空时技能首次使用会自动生成并保存。
-QIWE_UID=
+QIWEI_UID=
 
 # 可选:通常会自动复用 Claude Code 已配置的 Fmode NewAPI sk- token。
 # 也可以显式配置平台 sessionToken 或 Fmode API token。
-QIWE_AUTH_TOKEN=
+QIWEI_AUTH_TOKEN=
 
 # 企业微信接口访问凭据和设备上下文仅由 Fmode 网关管理,客户端无需配置。
 
 # 可选:覆盖企业微信官方 CLI 程序缓存目录。
-QIWE_OFFICIAL_CLI_RUNTIME_DIR=
+QIWEI_OFFICIAL_CLI_RUNTIME_DIR=
 
 # 可选:覆盖官方 CLI 的本地加密认证目录;默认 ~/.config/wecom。
 WECOM_CLI_CONFIG_DIR=

+ 1 - 1
claude-code/claude-code-qiwe-assistant/.mcp.json

@@ -1,6 +1,6 @@
 {
   "mcpServers": {
-    "qiwe": {
+    "qiwei": {
       "command": "node",
       "args": [
         "./mcp/src/server.js"

+ 43 - 43
claude-code/claude-code-qiwe-assistant/README.md

@@ -23,10 +23,10 @@ Claude Code / MCP
 ```text
 ├── .mcp.json
 ├── .env.example
-├── bin/qiwe-official-cli.js
+├── bin/qiwei-official-cli.js
 ├── wecom-cli-runtime.json
 ├── mcp/
-│   ├── catalog/qiwe-endpoints.json
+│   ├── catalog/qiwei-endpoints.json
 │   └── src/
 │       ├── server.js
 │       ├── core/api-catalog.js
@@ -35,16 +35,16 @@ Claude Code / MCP
 │       ├── providers/fmode-wecom-gateway.js
 │       ├── providers/wecom-official-cli.js
 │       └── tools/
-│           ├── qiwe-api-catalog-run.js
-│           ├── qiwe-login-run.js
-│           ├── qiwe-subscription-run.js
+│           ├── qiwei-api-catalog-run.js
+│           ├── qiwei-login-run.js
+│           ├── qiwei-subscription-run.js
 │           └── wecom-official-cli-run.js
 ├── skills/
-│   ├── qiwe-api-catalog/SKILL.md
-│   ├── qiwe-login/SKILL.md
-│   ├── qiwe-capability-router/SKILL.md
-│   ├── qiwe-official-meeting/SKILL.md
-│   └── qiwe-official-doc/SKILL.md
+│   ├── qiwei-api-catalog/SKILL.md
+│   ├── qiwei-login/SKILL.md
+│   ├── qiwei-capability-router/SKILL.md
+│   ├── qiwei-official-meeting/SKILL.md
+│   └── qiwei-official-doc/SKILL.md
 ├── docs/                             # 文档(specs/ guides/ generated/,规则见 OUTPUT-STANDARD.md)
 │   └── OUTPUT-STANDARD.md
 └── outputs/                          # 运行期生成文件,按类别归档,git 忽略
@@ -54,8 +54,8 @@ Claude Code / MCP
 
 所有生成文件遵循 [docs/OUTPUT-STANDARD.md](docs/OUTPUT-STANDARD.md):
 
-- 运行期产物统一写入 `outputs/<类别>/`(`login`、`api-calls`、`subscription`、`meetings`、`docs`、`messages`、`smoke`、`tmp`),根目录可用 `QIWE_OUTPUTS_DIR` 覆盖;
-- 覆盖型文件用 latest 模式(如 `outputs/login/qiwe-login-qrcode.png`),按次归档用 run 模式(`outputs/<类别>/<YYYY-MM-DD>/<HHmmss>-<slug>/` + `manifest.json`);
+- 运行期产物统一写入 `outputs/<类别>/`(`login`、`api-calls`、`subscription`、`meetings`、`docs`、`messages`、`smoke`、`tmp`),根目录可用 `QIWEI_OUTPUTS_DIR` 覆盖;
+- 覆盖型文件用 latest 模式(如 `outputs/login/qiwei-login-qrcode.png`),按次归档用 run 模式(`outputs/<类别>/<YYYY-MM-DD>/<HHmmss>-<slug>/` + `manifest.json`);
 - 路径一律通过 `mcp/src/core/output-paths.js` 解析;
 - 校验:`npm run outputs:validate`。
 
@@ -63,20 +63,20 @@ Claude Code / MCP
 
 | 工具 | 说明 |
 |---|---|
-| `qiwe_api_search` | 检索 100+ 个企业微信接口 |
-| `qiwe_api_doc` | 查看参数、返回字段和调用模板 |
-| `qiwe_api_call` | `POST /api/qiwe/doApi`,传 `{uid, method, params}` |
-| `qiwe_login_status` | `GET /api/qiwe/login/status?uid=` |
-| `qiwe_login_start` | `POST /api/qiwe/login/start` 并保存二维码 |
-| `qiwe_login_check` | `POST /api/qiwe/login/check` |
-| `qiwe_login_verify` | `POST /api/qiwe/login/verify` |
-| `qiwe_subscription_status` | 查询订阅、席位、到期时间和余额 |
-| `qiwe_subscribe` | 开通、续费或增购席位 |
-| `qiwe_subscription_auto_renew` | 设置自动续费 |
-| `qiwe_official_status` | 检查官方 CLI 下载与授权状态 |
-| `qiwe_official_prepare` | 下载并缓存固定版本官方 CLI |
-| `qiwe_official_help` | 读取官方 category/method 帮助 |
-| `qiwe_official_call` | 结构化调用官方通讯录、文档、会议、消息、日程和待办能力 |
+| `qiwei_api_search` | 检索 100+ 个企业微信接口 |
+| `qiwei_api_doc` | 查看参数、返回字段和调用模板 |
+| `qiwei_api_call` | `POST /api/qiwei/doApi`,传 `{uid, method, params}` |
+| `qiwei_login_status` | `GET /api/qiwei/login/status?uid=` |
+| `qiwei_login_start` | `POST /api/qiwei/login/start` 并保存二维码 |
+| `qiwei_login_check` | `POST /api/qiwei/login/check` |
+| `qiwei_login_verify` | `POST /api/qiwei/login/verify` |
+| `qiwei_subscription_status` | 查询订阅、席位、到期时间和余额 |
+| `qiwei_subscribe` | 开通、续费或增购席位 |
+| `qiwei_subscription_auto_renew` | 设置自动续费 |
+| `qiwei_official_status` | 检查官方 CLI 下载与授权状态 |
+| `qiwei_official_prepare` | 下载并缓存固定版本官方 CLI |
+| `qiwei_official_help` | 读取官方 category/method 帮助 |
+| `qiwei_official_call` | 结构化调用官方通讯录、文档、会议、消息、日程和待办能力 |
 
 ## 官方 CLI 通道
 
@@ -84,11 +84,11 @@ Claude Code / MCP
 
 运行时版本固定在 `wecom-cli-runtime.json`,默认安装到用户缓存:
 
-- Windows:`%LOCALAPPDATA%\Fmode\qiwe-assistant\wecom-cli\<version>`
-- macOS:`~/Library/Caches/fmode/qiwe-assistant/wecom-cli/<version>`
-- Linux:`~/.cache/fmode/qiwe-assistant/wecom-cli/<version>`
+- Windows:`%LOCALAPPDATA%\Fmode\qiwei-assistant\wecom-cli\<version>`
+- macOS:`~/Library/Caches/fmode/qiwei-assistant/wecom-cli/<version>`
+- Linux:`~/.cache/fmode/qiwei-assistant/wecom-cli/<version>`
 
-可以让 Skill 首次使用时调用 `qiwe_official_prepare`,也可以提前下载:
+可以让 Skill 首次使用时调用 `qiwei_official_prepare`,也可以提前下载:
 
 ```bash
 npm run wecom:install
@@ -106,25 +106,25 @@ npm run wecom:init
 npm run wecom:status
 ```
 
-官方 CLI 的认证默认保存在 `~/.config/wecom`,也可由 `WECOM_CLI_CONFIG_DIR` 覆盖。该认证与 Fmode token、`QIWE_UID` 和个人企微设备登录完全独立。
+官方 CLI 的认证默认保存在 `~/.config/wecom`,也可由 `WECOM_CLI_CONFIG_DIR` 覆盖。该认证与 Fmode token、`QIWEI_UID` 和个人企微设备登录完全独立。
 
 ## 鉴权和本地配置
 
 - 请求头统一为 `Authorization: Bearer <Fmode token>`。
 - 优先自动读取 Claude Code 已配置的 Fmode NewAPI `sk-` token。
-- 也支持 `QIWE_AUTH_TOKEN`、`FMODE_API_KEY`、`FMODE_API_TOKEN`、`NEWAPI_TOKEN` 或平台 sessionToken。
-- `QIWE_API_BASE` 默认 `https://server.fmode.cn/api/qiwe`。
-- `QIWE_UID` 是客户端设备别名;未配置时会生成随机稳定 uid,并写入 `.env.local` 和 `~/.claude/qiwe-credentials.json`。
+- 也支持 `QIWEI_AUTH_TOKEN`、`FMODE_API_KEY`、`FMODE_API_TOKEN`、`NEWAPI_TOKEN` 或平台 sessionToken。
+- `QIWEI_API_BASE` 默认 `https://server.fmode.cn/api/qiwei`。
+- `QIWEI_UID` 是客户端设备别名;未配置时会生成随机稳定 uid,并写入 `.env.local` 和 `~/.claude/qiwei-credentials.json`。
 - 企业微信接口访问凭据和设备上下文由 Fmode 网关管理,不会出现在技能配置或返回结果中。
 
 ## 首次使用
 
-1. `qiwe_subscription_status` 检查订阅;
-2. 未订阅时调用 `qiwe_subscribe`,`seats` 表示企微账号席位数;
-3. `qiwe_login_start` 生成二维码;
-4. 用户扫码后每 3–5 秒调用 `qiwe_login_check`;
-5. 状态 `10` 时用 `qiwe_login_verify` 提交 6 位验证码;
-6. 状态 `2` 后用 `qiwe_api_call` 调业务接口。
+1. `qiwei_subscription_status` 检查订阅;
+2. 未订阅时调用 `qiwei_subscribe`,`seats` 表示企微账号席位数;
+3. `qiwei_login_start` 生成二维码;
+4. 用户扫码后每 3–5 秒调用 `qiwei_login_check`;
+5. 状态 `10` 时用 `qiwei_login_verify` 提交 6 位验证码;
+6. 状态 `2` 后用 `qiwei_api_call` 调业务接口。
 
 业务调用只传清单中的业务参数:
 
@@ -153,8 +153,8 @@ npm run outputs:validate
 
 ## 限制
 
-- `cloud` 模块的 multipart 文件直传尚未由 Fmode 网关支持,`qiwe_api_call` 会拒绝这类接口。
-- `/login/*`、`/client/*` 不允许通过 `qiwe_api_call` 透传,必须使用登录专用工具。
-- 服务端未挂载前,默认生产地址会返回不可用;可通过 `QIWE_API_BASE` 指向测试环境。
+- `cloud` 模块的 multipart 文件直传尚未由 Fmode 网关支持,`qiwei_api_call` 会拒绝这类接口。
+- `/login/*`、`/client/*` 不允许通过 `qiwei_api_call` 透传,必须使用登录专用工具。
+- 服务端未挂载前,默认生产地址会返回不可用;可通过 `QIWEI_API_BASE` 指向测试环境。
 - 官方 CLI 首次下载需要 npm 网络访问,首次业务调用前需要独立完成企业微信机器人扫码授权。
 - 官方 CLI 通道失败不会替代或改变原有 Fmode 网关接口通道。

+ 1 - 1
claude-code/claude-code-qiwe-assistant/bin/qiwe-official-cli.js → claude-code/claude-code-qiwe-assistant/bin/qiwei-official-cli.js

@@ -43,6 +43,6 @@ async function main() {
 }
 
 main().catch(error => {
-  console.error(`qiwe-official-cli failed: ${sanitizeCliText(error.message)}`);
+  console.error(`qiwei-official-cli failed: ${sanitizeCliText(error.message)}`);
   process.exit(1);
 });

+ 6 - 6
claude-code/claude-code-qiwe-assistant/docs/OUTPUT-STANDARD.md

@@ -1,11 +1,11 @@
-# 输出与目录标准(claude-code-qiwe-assistant)
+# 输出与目录标准(claude-code-qiwei-assistant)
 
 本文件定义技能包所有生成文件的目录规则。任何工具、脚本或 Skill 生成文件时必须遵循本标准,并通过 `mcp/src/core/output-paths.js` 模块解析路径,禁止直接拼 `process.cwd() + 'outputs'`。
 
 ## 1. 顶层目录职责
 
 ```text
-claude-code-qiwe-assistant/
+claude-code-qiwei-assistant/
 ├── bin/          # CLI 入口,不生成文件
 ├── mcp/          # MCP 服务源码与接口清单(catalog 为构建产物,由 scripts/build-catalog.py 生成)
 ├── skills/       # SKILL.md 定义,只读
@@ -16,14 +16,14 @@ claude-code-qiwe-assistant/
 
 ## 2. /outputs 生成规则
 
-统一根目录:`<包根>/outputs`,可用环境变量 `QIWE_OUTPUTS_DIR` 覆盖。目录不存在时由 `output-paths.js` 自动创建。
+统一根目录:`<包根>/outputs`,可用环境变量 `QIWEI_OUTPUTS_DIR` 覆盖。目录不存在时由 `output-paths.js` 自动创建。
 
 第一层为固定类别,禁止在 outputs 根目录直接放文件:
 
 | 类别 | 用途 |
 |---|---|
 | `login/` | 扫码登录二维码与预览页(保持固定文件名,最新一次覆盖) |
-| `api-calls/` | `qiwe_api_call` 落盘的请求/响应样本 |
+| `api-calls/` | `qiwei_api_call` 落盘的请求/响应样本 |
 | `subscription/` | 订阅、席位、余额查询快照 |
 | `meetings/` | 官方 CLI 会议相关导出 |
 | `docs/` | 官方 CLI 文档能力导出 |
@@ -42,7 +42,7 @@ claude-code-qiwe-assistant/
 ```json
 {
   "createdAt": "2026-07-12T09:00:00.000Z",
-  "tool": "qiwe_official_call",
+  "tool": "qiwei_official_call",
   "summary": {},
   "files": ["meeting-list.json"]
 }
@@ -64,7 +64,7 @@ claude-code-qiwe-assistant/
 `mcp/src/core/output-paths.js` 暴露:
 
 ```js
-outputsRoot()                       // outputs 根目录(含 QIWE_OUTPUTS_DIR 覆盖)
+outputsRoot()                       // outputs 根目录(含 QIWEI_OUTPUTS_DIR 覆盖)
 categoryDir(category)               // 确保并返回类别目录
 latestPath(category, filename)      // latest 模式路径
 createRunDir(category, slug, date?) // run 模式目录

+ 10 - 10
claude-code/claude-code-qiwe-assistant/docs/specs/qiwe-subscription-flow.md → claude-code/claude-code-qiwe-assistant/docs/specs/qiwei-subscription-flow.md

@@ -2,7 +2,7 @@
 
 ## 流程页服务(默认模式)
 
-`qiwe_login_start` 默认在本地端口(`QIWE_FLOW_PORT`,默认 4310)启动
+`qiwei_login_start` 默认在本地端口(`QIWEI_FLOW_PORT`,默认 4310)启动
 流程页服务(`mcp/src/core/login-flow-server.js`)并自动打开浏览器:
 
 - 页面加载时请求 `/flow/state` 自动判断当前所处步骤
@@ -18,22 +18,22 @@
 ## 流程总览(静态模式)
 
 1. **第 1 步:开通订阅(付费)**
-   - `qiwe_login_start` 会先查询 `GET /subscribe/status`;
+   - `qiwei_login_start` 会先查询 `GET /subscribe/status`;
    - 未开通/已到期时,不直接报错,而是生成套餐选择页
-     `outputs/subscription/qiwe-subscribe.html` 并自动打开浏览器;
+     `outputs/subscription/qiwei-subscribe.html` 并自动打开浏览器;
    - 页面上选择席位(1/3/10/20/50 个号)与时长(1/6/12 个月),
      点击「开通服务」后调用 `POST /subscribe`,按
      `席位数 × ¥200/月 × 月数` 从飞马余额一次性扣费;
-   - 开通成功后重新调用 `qiwe_login_start` 进入第 2 步。
+   - 开通成功后重新调用 `qiwei_login_start` 进入第 2 步。
 
 2. **第 2 步:扫码登录**
-   - `qiwe_login_start` 生成二维码(`outputs/login/qiwe-login-qrcode.png` + 预览页);
-   - 手机企业微信扫码确认后,`qiwe_login_check` 轮询状态;
-   - 状态 10 时用 `qiwe_login_verify` 提交 6 位验证码。
+   - `qiwei_login_start` 生成二维码(`outputs/login/qiwei-login-qrcode.png` + 预览页);
+   - 手机企业微信扫码确认后,`qiwei_login_check` 轮询状态;
+   - 状态 10 时用 `qiwei_login_verify` 提交 6 位验证码。
 
 ## 价格
 
-- 单价:每个账号(席位)**¥200/月**(`QIWE_MONTHLY_PRICE`)。
+- 单价:每个账号(席位)**¥200/月**(`QIWEI_MONTHLY_PRICE`)。
 - 页面套餐:1 号 ¥200/月、3 号 ¥600/月、10 号 ¥2000/月(推荐)、
   20 号 ¥4000/月、50 号 ¥10000/月;时长 1/6/12 个月。
 
@@ -41,7 +41,7 @@
 
 | 错误码 | HTTP | 含义 | 用户提示 |
 | --- | --- | --- | --- |
-| QW-AUTH-401 | 401 | 鉴权失败 | token 无效或过期,检查 QIWE_AUTH_TOKEN 后重试 |
+| QW-AUTH-401 | 401 | 鉴权失败 | token 无效或过期,检查 QIWEI_AUTH_TOKEN 后重试 |
 | QW-PAY-402 | 402 | 飞马余额不足 | 余额不足以完成扣费,请先充值飞马余额 |
 | QW-SUB-402 | 402 | 订阅未开通或已到期 | 在套餐页选择席位并点击开通 |
 | QW-SEAT-403 | 403 | 席位已满 | 增购席位或停用闲置设备 |
@@ -52,7 +52,7 @@
 
 ## MCP 工具侧状态
 
-- `needs_subscription`:`qiwe_login_start` 检测到未订阅时返回,
+- `needs_subscription`:`qiwei_login_start` 检测到未订阅时返回,
   `summary.subscribePage` 指向套餐页路径;
 - `needs_seat`:席位不足;
 - `needs_auth`:鉴权失败;

+ 3 - 3
claude-code/claude-code-qiwe-assistant/mcp/catalog/qiwe-endpoints.json → claude-code/claude-code-qiwe-assistant/mcp/catalog/qiwei-endpoints.json

@@ -3,11 +3,11 @@
  "updatedAt": "2026-07-10",
  "source": "企业微信开放接口资料",
  "gateway": {
-  "baseUrl": "https://server.fmode.cn/api/qiwe",
+  "baseUrl": "https://server.fmode.cn/api/qiwei",
   "endpoint": "POST {baseUrl}/doApi",
   "auth": "Authorization: Bearer <Fmode token>",
   "envelope": "Fmode 网关请求信封:{\"uid\": \"<本地稳定设备标识>\", \"method\": \"<接口 method 路径>\", \"params\": {...}};企业微信接口访问凭据和设备上下文由 Fmode 网关管理。",
-  "note": "所有业务请求均通过 Fmode 网关转发的企业微信接口执行。清单未登记的普通 JSON 接口可用 qiwe_api_call 传 rawMethod + params;登录和设备接口必须使用 qiwe_login_* 专用工具。"
+  "note": "所有业务请求均通过 Fmode 网关转发的企业微信接口执行。清单未登记的普通 JSON 接口可用 qiwei_api_call 传 rawMethod + params;登录和设备接口必须使用 qiwei_login_* 专用工具。"
  },
  "modules": [
   "client",
@@ -5163,7 +5163,7 @@
    "id": "cloud.bigFileDownloadAsync",
    "module": "cloud",
    "title": "企微大文件异步下载",
-   "summary": "文件大于 20M 使用此接口;下载地址为临时云资源(7–15 天不定期清理);异步结果通过回调通知,根据 requestId 匹配。注意:官方文档未公布该接口的 method 路由值,qiwe_api_call 无法直接调用,请参考 docUrl 官方文档确认后用 rawMethod 调用。",
+   "summary": "文件大于 20M 使用此接口;下载地址为临时云资源(7–15 天不定期清理);异步结果通过回调通知,根据 requestId 匹配。注意:官方文档未公布该接口的 method 路由值,qiwei_api_call 无法直接调用,请参考 docUrl 官方文档确认后用 rawMethod 调用。",
    "method": null,
    "category": [
     "API参考",

+ 1 - 1
claude-code/claude-code-qiwe-assistant/mcp/src/core/api-catalog.js

@@ -1,7 +1,7 @@
 const fs = require('fs');
 const path = require('path');
 
-const CATALOG_PATH = path.resolve(__dirname, '..', '..', 'catalog', 'qiwe-endpoints.json');
+const CATALOG_PATH = path.resolve(__dirname, '..', '..', 'catalog', 'qiwei-endpoints.json');
 
 let cached;
 

+ 27 - 19
claude-code/claude-code-qiwe-assistant/mcp/src/core/credentials.js

@@ -3,8 +3,8 @@ const fs = require('fs');
 const path = require('path');
 const os = require('os');
 
-const DEFAULT_API_BASE = 'https://server.fmode.cn/api/qiwe';
-const CREDENTIALS_FILE = path.join(os.homedir(), '.claude', 'qiwe-credentials.json');
+const DEFAULT_API_BASE = 'https://server.fmode.cn/api/qiwei';
+const CREDENTIALS_FILE = path.join(os.homedir(), '.claude', 'qiwei-credentials.json');
 
 function readJsonMaybe(filePath) {
   try {
@@ -112,7 +112,7 @@ function normalizeToken(value) {
   return String(value || '').trim().replace(/^Bearer\s+/i, '');
 }
 
-function readQiweAuthToken(input = {}) {
+function readQiweiAuthToken(input = {}) {
   const fileEnv = readEnvFiles();
   const claudeEnv = readClaudeSettingsEnv();
   const fmodeConfig = readFmodeConfig();
@@ -123,12 +123,14 @@ function readQiweAuthToken(input = {}) {
     input.sessionToken,
     input.apiToken,
     input.token,
+    fileEnv.QIWEI_AUTH_TOKEN,
     fileEnv.QIWE_AUTH_TOKEN,
     fileEnv.FMODE_API_KEY,
     fileEnv.FMODE_API_TOKEN,
     fileEnv.NEWAPI_TOKEN,
     fileEnv.VOC_TOKEN,
     fileEnv.VOC_SOCIAL_TOKEN,
+    process.env.QIWEI_AUTH_TOKEN,
     process.env.QIWE_AUTH_TOKEN,
     process.env.FMODE_API_KEY,
     process.env.FMODE_API_TOKEN,
@@ -151,26 +153,32 @@ function readCredentialsFile() {
   return readJsonMaybe(CREDENTIALS_FILE);
 }
 
-function readQiweUid(input = {}) {
+function readQiweiUid(input = {}) {
   const fileEnv = readEnvFiles();
   const creds = readCredentialsFile();
   return firstNonEmpty([
     input.uid,
-    input.qiweUid,
+    input.qiweiUid,
+    fileEnv.QIWEI_UID,
     fileEnv.QIWE_UID,
+    process.env.QIWEI_UID,
     process.env.QIWE_UID,
     creds.uid
   ]);
 }
 
-function readQiweApiBase(input = {}) {
+function readQiweiApiBase(input = {}) {
   const fileEnv = readEnvFiles();
   const creds = readCredentialsFile();
   return String(firstNonEmpty([
     input.apiBase,
     input.baseUrl,
+    fileEnv.QIWEI_API_BASE,
+    fileEnv.QIWEI_RELAY_BASE_URL,
     fileEnv.QIWE_API_BASE,
     fileEnv.QIWE_RELAY_BASE_URL,
+    process.env.QIWEI_API_BASE,
+    process.env.QIWEI_RELAY_BASE_URL,
     process.env.QIWE_API_BASE,
     process.env.QIWE_RELAY_BASE_URL,
     creds.apiBase,
@@ -178,7 +186,7 @@ function readQiweApiBase(input = {}) {
   ]) || DEFAULT_API_BASE).replace(/\/$/, '');
 }
 
-function saveQiweClientConfig({ uid, apiBase } = {}) {
+function saveQiweiClientConfig({ uid, apiBase } = {}) {
   const saved = [];
   try {
     const dir = path.dirname(CREDENTIALS_FILE);
@@ -196,8 +204,8 @@ function saveQiweClientConfig({ uid, apiBase } = {}) {
   try {
     const envPath = path.join(process.cwd(), '.env.local');
     const pairs = [];
-    if (uid) pairs.push(['QIWE_UID', uid]);
-    if (apiBase) pairs.push(['QIWE_API_BASE', apiBase]);
+    if (uid) pairs.push(['QIWEI_UID', uid]);
+    if (apiBase) pairs.push(['QIWEI_API_BASE', apiBase]);
     if (pairs.length) {
       let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
       for (const [key, value] of pairs) {
@@ -216,26 +224,26 @@ function saveQiweClientConfig({ uid, apiBase } = {}) {
   return saved;
 }
 
-function ensureQiweUid(input = {}) {
-  const existing = readQiweUid(input);
+function ensureQiweiUid(input = {}) {
+  const existing = readQiweiUid(input);
   if (existing) return existing;
-  const uid = `qiwe-${crypto.randomUUID()}`;
-  saveQiweClientConfig({ uid, apiBase: readQiweApiBase(input) });
+  const uid = `qiwei-${crypto.randomUUID()}`;
+  saveQiweiClientConfig({ uid, apiBase: readQiweiApiBase(input) });
   return uid;
 }
 
 function isConfigured(input = {}) {
-  return Boolean(readQiweAuthToken(input) && readQiweUid(input));
+  return Boolean(readQiweiAuthToken(input) && readQiweiUid(input));
 }
 
 module.exports = {
   DEFAULT_API_BASE,
   CREDENTIALS_FILE,
-  readQiweAuthToken,
-  readQiweUid,
-  ensureQiweUid,
-  readQiweApiBase,
-  saveQiweClientConfig,
+  readQiweiAuthToken,
+  readQiweiUid,
+  ensureQiweiUid,
+  readQiweiApiBase,
+  saveQiweiClientConfig,
   isConfigured,
   readEnvFiles,
   readClaudeSettingsEnv,

+ 4 - 4
claude-code/claude-code-qiwe-assistant/mcp/src/core/login-flow-server.js

@@ -1,6 +1,6 @@
 const http = require('http');
 const { callFmodeWecomGateway } = require('../providers/fmode-wecom-gateway');
-const { SEAT_PLANS, DURATION_PLANS, QIWE_MONTHLY_PRICE, classifySubscribeError, ERROR_CODES } = require('./subscribe-page');
+const { SEAT_PLANS, DURATION_PLANS, QIWEI_MONTHLY_PRICE, classifySubscribeError, ERROR_CODES } = require('./subscribe-page');
 
 const DEFAULT_FLOW_PORT = 4310;
 
@@ -221,7 +221,7 @@ function renderVerify() {
 function renderSuccess(detail) {
   setStep(4);
   panel.innerHTML = '<div class="center"><div class="success">✔ 企业微信登录成功</div>' +
-    '<div class="hint">账号:' + (detail.nickname || detail.userId || '未知') + (detail.corpName ? '(' + detail.corpName + ')' : '') + '<br>现在可以回到对话中使用企微接口能力(qiwe_api_call、消息、会议等)。</div></div>';
+    '<div class="hint">账号:' + (detail.nickname || detail.userId || '未知') + (detail.corpName ? '(' + detail.corpName + ')' : '') + '<br>现在可以回到对话中使用企微接口能力(qiwei_api_call、消息、会议等)。</div></div>';
 }
 
 (async () => {
@@ -262,7 +262,7 @@ function createFlowHandler(ctx) {
     try {
       if (url.pathname === '/' || url.pathname === '/index.html') {
         res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
-        res.end(buildFlowPageHtml({ monthlyPrice: QIWE_MONTHLY_PRICE }));
+        res.end(buildFlowPageHtml({ monthlyPrice: QIWEI_MONTHLY_PRICE }));
         return;
       }
       if (url.pathname === '/flow/state') {
@@ -354,7 +354,7 @@ function createFlowHandler(ctx) {
 }
 
 function startLoginFlowServer({ token, apiBase, uid, port, onQrcode } = {}) {
-  const listenPort = Number(port || process.env.QIWE_FLOW_PORT) || DEFAULT_FLOW_PORT;
+  const listenPort = Number(port || process.env.QIWEI_FLOW_PORT) || DEFAULT_FLOW_PORT;
   if (currentServer && currentContext) {
     Object.assign(currentContext, { token, apiBase, uid, onQrcode });
     return Promise.resolve({ url: `http://127.0.0.1:${currentContext.port}/`, alreadyRunning: true });

+ 1 - 1
claude-code/claude-code-qiwe-assistant/mcp/src/core/output-paths.js

@@ -15,7 +15,7 @@ const OUTPUT_CATEGORIES = Object.freeze([
 ]);
 
 function outputsRoot() {
-  const override = process.env.QIWE_OUTPUTS_DIR;
+  const override = process.env.QIWEI_OUTPUTS_DIR;
   if (override && String(override).trim()) return path.resolve(String(override).trim());
   return path.join(PACKAGE_ROOT, 'outputs');
 }

+ 6 - 6
claude-code/claude-code-qiwe-assistant/mcp/src/core/subscribe-page.js

@@ -1,7 +1,7 @@
 const fs = require('fs');
 const { latestPath } = require('./output-paths');
 
-const QIWE_MONTHLY_PRICE = 200;
+const QIWEI_MONTHLY_PRICE = 200;
 
 const SEAT_PLANS = [
   { seats: 1, label: '1 个号', note: '个人试用' },
@@ -18,7 +18,7 @@ const DURATION_PLANS = [
 ];
 
 const ERROR_CODES = {
-  'QW-AUTH-401': { http: 401, title: '认证失败', tip: '鉴权 token 无效或已过期,请检查 QIWE_AUTH_TOKEN 后重试。' },
+  'QW-AUTH-401': { http: 401, title: '认证失败', tip: '鉴权 token 无效或已过期,请检查 QIWEI_AUTH_TOKEN 后重试。' },
   'QW-PAY-402': { http: 402, title: '飞马余额不足', tip: '账户余额不足以完成本次订阅扣费,请先充值飞马余额。' },
   'QW-SUB-402': { http: 402, title: '订阅未开通或已到期', tip: '请在套餐页面选择席位并点击开通,扣费成功后即可继续登录。' },
   'QW-SEAT-403': { http: 403, title: '席位已满', tip: '当前订阅席位已全部占用,请增购席位或停用闲置设备。' },
@@ -34,7 +34,7 @@ function classifySubscribeError(httpStatus, message) {
   return 'QW-UP-502';
 }
 
-function buildSubscribePageHtml({ apiBase, token, monthlyPrice = QIWE_MONTHLY_PRICE }) {
+function buildSubscribePageHtml({ apiBase, token, monthlyPrice = QIWEI_MONTHLY_PRICE }) {
   const plansJson = JSON.stringify(SEAT_PLANS);
   const durationsJson = JSON.stringify(DURATION_PLANS);
   const errorsJson = JSON.stringify(ERROR_CODES);
@@ -155,7 +155,7 @@ document.getElementById('buy').onclick = async () => {
       }
     }
     const d = (lastData && lastData.data) || {};
-    showMsg('ok', '<b>开通成功!</b><br>席位:' + (d.seats || seats) + ',到期时间:' + (d.expireAt || '已延长 ' + months + ' 个月') + '。<br>请回到对话中重新执行 qiwe_login_start 继续扫码登录。');
+    showMsg('ok', '<b>开通成功!</b><br>席位:' + (d.seats || seats) + ',到期时间:' + (d.expireAt || '已延长 ' + months + ' 个月') + '。<br>请回到对话中重新执行 qiwei_login_start 继续扫码登录。');
   } catch (e) {
     showMsg('err', '<b>[QW-UP-502] 网络请求失败</b><br>' + e.message + '<br>请确认网关服务可用后重试。');
     btn.disabled = false;
@@ -179,7 +179,7 @@ render();
 
 function saveSubscribePage({ apiBase, token, monthlyPrice } = {}) {
   try {
-    const htmlPath = latestPath('subscription', 'qiwe-subscribe.html');
+    const htmlPath = latestPath('subscription', 'qiwei-subscribe.html');
     fs.writeFileSync(htmlPath, buildSubscribePageHtml({ apiBase, token, monthlyPrice }), 'utf8');
     return htmlPath;
   } catch {
@@ -188,7 +188,7 @@ function saveSubscribePage({ apiBase, token, monthlyPrice } = {}) {
 }
 
 module.exports = {
-  QIWE_MONTHLY_PRICE,
+  QIWEI_MONTHLY_PRICE,
   SEAT_PLANS,
   DURATION_PLANS,
   ERROR_CODES,

+ 6 - 6
claude-code/claude-code-qiwe-assistant/mcp/src/core/wecom-cli-runtime.js

@@ -28,21 +28,21 @@ function isSupportedPlatform() {
 }
 
 function getRuntimeRoot() {
-  if (process.env.QIWE_OFFICIAL_CLI_RUNTIME_DIR) {
-    return path.resolve(expandHome(process.env.QIWE_OFFICIAL_CLI_RUNTIME_DIR));
+  if (process.env.QIWEI_OFFICIAL_CLI_RUNTIME_DIR) {
+    return path.resolve(expandHome(process.env.QIWEI_OFFICIAL_CLI_RUNTIME_DIR));
   }
 
   if (process.platform === 'win32') {
     const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
-    return path.join(base, 'Fmode', 'qiwe-assistant', 'wecom-cli');
+    return path.join(base, 'Fmode', 'qiwei-assistant', 'wecom-cli');
   }
 
   if (process.platform === 'darwin') {
-    return path.join(os.homedir(), 'Library', 'Caches', 'fmode', 'qiwe-assistant', 'wecom-cli');
+    return path.join(os.homedir(), 'Library', 'Caches', 'fmode', 'qiwei-assistant', 'wecom-cli');
   }
 
   const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
-  return path.join(base, 'fmode', 'qiwe-assistant', 'wecom-cli');
+  return path.join(base, 'fmode', 'qiwei-assistant', 'wecom-cli');
 }
 
 function getRuntimeDir() {
@@ -369,7 +369,7 @@ async function getOfficialCliStatus() {
 }
 
 function getInitCommand() {
-  const wrapperPath = path.resolve(__dirname, '../../../bin/qiwe-official-cli.js');
+  const wrapperPath = path.resolve(__dirname, '../../../bin/qiwei-official-cli.js');
   return `"${process.execPath}" "${wrapperPath}" init`;
 }
 

+ 9 - 9
claude-code/claude-code-qiwe-assistant/mcp/src/providers/wecom-official-cli.js

@@ -32,12 +32,12 @@ function initializationError() {
   });
 }
 
-async function qiweOfficialStatus() {
+async function qiweiOfficialStatus() {
   const status = await getOfficialCliStatus();
   const nextActions = [];
   if (!status.installed || !status.valid) {
     nextActions.push({
-      action: 'qiwe_official_prepare',
+      action: 'qiwei_official_prepare',
       description: `下载并缓存 ${runtimeManifest.package}@${runtimeManifest.version}`
     });
   } else if (!status.ready) {
@@ -62,7 +62,7 @@ async function qiweOfficialStatus() {
   });
 }
 
-async function qiweOfficialPrepare({ force = false } = {}) {
+async function qiweiOfficialPrepare({ force = false } = {}) {
   try {
     const installed = await prepareOfficialCli({ force });
     const status = await getOfficialCliStatus();
@@ -97,7 +97,7 @@ async function qiweOfficialPrepare({ force = false } = {}) {
   }
 }
 
-async function qiweOfficialHelp({ category, method } = {}) {
+async function qiweiOfficialHelp({ category, method } = {}) {
   if (method && !category) return errorResult('查询方法帮助时必须同时提供 category。');
   if (category && !OFFICIAL_CATEGORIES.has(category)) {
     return errorResult(`不支持的官方 CLI category:${category}`);
@@ -143,7 +143,7 @@ async function qiweOfficialHelp({ category, method } = {}) {
   }
 }
 
-async function qiweOfficialCall({ category, method, args = {}, timeoutMs } = {}) {
+async function qiweiOfficialCall({ category, method, args = {}, timeoutMs } = {}) {
   if (!OFFICIAL_CATEGORIES.has(category)) {
     return errorResult(`不支持的官方 CLI category:${category || '未提供'}`);
   }
@@ -200,8 +200,8 @@ async function qiweOfficialCall({ category, method, args = {}, timeoutMs } = {})
 
 module.exports = {
   OFFICIAL_CATEGORIES,
-  qiweOfficialStatus,
-  qiweOfficialPrepare,
-  qiweOfficialHelp,
-  qiweOfficialCall
+  qiweiOfficialStatus,
+  qiweiOfficialPrepare,
+  qiweiOfficialHelp,
+  qiweiOfficialCall
 };

+ 45 - 45
claude-code/claude-code-qiwe-assistant/mcp/src/server.js

@@ -2,23 +2,23 @@
 const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
 const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
 const { z } = require('zod');
-const { searchQiweApis, getQiweApiDoc, callQiweApi } = require('./tools/qiwe-api-catalog-run');
+const { searchQiweiApis, getQiweiApiDoc, callQiweiApi } = require('./tools/qiwei-api-catalog-run');
 const {
-  qiweLoginStatus,
-  qiweLoginStart,
-  qiweLoginCheck,
-  qiweLoginVerify
-} = require('./tools/qiwe-login-run');
+  qiweiLoginStatus,
+  qiweiLoginStart,
+  qiweiLoginCheck,
+  qiweiLoginVerify
+} = require('./tools/qiwei-login-run');
 const {
-  qiweSubscriptionStatus,
-  qiweSubscribe,
-  qiweSubscriptionAutoRenew
-} = require('./tools/qiwe-subscription-run');
+  qiweiSubscriptionStatus,
+  qiweiSubscribe,
+  qiweiSubscriptionAutoRenew
+} = require('./tools/qiwei-subscription-run');
 const {
-  qiweOfficialStatus,
-  qiweOfficialPrepare,
-  qiweOfficialHelp,
-  qiweOfficialCall
+  qiweiOfficialStatus,
+  qiweiOfficialPrepare,
+  qiweiOfficialHelp,
+  qiweiOfficialCall
 } = require('./tools/wecom-official-cli-run');
 
 function asToolResult(result) {
@@ -56,7 +56,7 @@ function createServer() {
   });
 
   server.registerTool(
-    'qiwe_api_search',
+    'qiwei_api_search',
     {
       title: '检索企业微信开放接口清单',
       description: [
@@ -71,24 +71,24 @@ function createServer() {
         limit: z.number().int().min(1).max(200).optional()
       }
     },
-    wrap(searchQiweApis)
+    wrap(searchQiweiApis)
   );
 
   server.registerTool(
-    'qiwe_api_doc',
+    'qiwei_api_doc',
     {
       title: '读取企业微信接口的详细参数文档',
-      description: '读取清单中某个企业微信接口的完整参数文档(参数表、必填项、返回字段说明、调用模板)。入参 id(如 msg.sendText,来自 qiwe_api_search)或 method 路径(如 /msg/sendText)。',
+      description: '读取清单中某个企业微信接口的完整参数文档(参数表、必填项、返回字段说明、调用模板)。入参 id(如 msg.sendText,来自 qiwei_api_search)或 method 路径(如 /msg/sendText)。',
       inputSchema: {
         id: z.string().optional(),
         method: z.string().optional()
       }
     },
-    wrap(getQiweApiDoc)
+    wrap(getQiweiApiDoc)
   );
 
   server.registerTool(
-    'qiwe_api_call',
+    'qiwei_api_call',
     {
       title: '调用企业微信开放接口',
       description: [
@@ -105,11 +105,11 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(callQiweApi)
+    wrap(callQiweiApi)
   );
 
   server.registerTool(
-    'qiwe_login_status',
+    'qiwei_login_status',
     {
       title: '查询企业微信登录/配置状态',
       description: '通过 Fmode 网关转发的企业微信接口查询当前 uid 对应的设备在线状态。技能包安装后、或任何接口报设备/登录错误时先调用。',
@@ -119,16 +119,16 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(qiweLoginStatus)
+    wrap(qiweiLoginStatus)
   );
 
   server.registerTool(
-    'qiwe_login_start',
+    'qiwei_login_start',
     {
       title: '开始企业微信扫码登录',
       description: [
         '登录两步流程入口:默认在本地端口(默认 4310)启动流程页服务并打开浏览器,页面自动推进:未开通订阅时先展示套餐页(每号 ¥200/月,飞马余额扣费),开通后自动生成二维码并每 3 秒轮询扫码状态,需要验证码时页面提示输入。',
-        '传 flowUi=false 回退到静态模式:校验订阅(未开通返回 needs_subscription 并生成套餐页)后生成二维码保存到 outputs/login/qiwe-login-qrcode.png(openBrowser=false 可关闭自动打开)。',
+        '传 flowUi=false 回退到静态模式:校验订阅(未开通返回 needs_subscription 并生成套餐页)后生成二维码保存到 outputs/login/qiwei-login-qrcode.png(openBrowser=false 可关闭自动打开)。',
         '需要有效企微包月订阅和可用席位;无需提供企业微信接口访问凭据、代理或设备参数。'
       ].join(' '),
       inputSchema: {
@@ -140,11 +140,11 @@ function createServer() {
         flowPort: z.number().int().optional()
       }
     },
-    wrap(qiweLoginStart)
+    wrap(qiweiLoginStart)
   );
 
   server.registerTool(
-    'qiwe_login_check',
+    'qiwei_login_check',
     {
       title: '检测扫码登录状态',
       description: '通过 Fmode 网关转发的企业微信接口轮询扫码登录状态(用户扫码后每 3-5 秒调一次)。状态 10 表示需要 6 位验证码;manual=true 时先尝试免扫码登录。',
@@ -155,14 +155,14 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(qiweLoginCheck)
+    wrap(qiweiLoginCheck)
   );
 
   server.registerTool(
-    'qiwe_login_verify',
+    'qiwei_login_verify',
     {
       title: '提交扫码登录的 6 位验证码',
-      description: '当 qiwe_login_check 返回状态 10(需要验证码)时,向用户索要手机端显示的 6 位验证码并通过本工具提交,随后再调 qiwe_login_check 确认登录成功。',
+      description: '当 qiwei_login_check 返回状态 10(需要验证码)时,向用户索要手机端显示的 6 位验证码并通过本工具提交,随后再调 qiwei_login_check 确认登录成功。',
       inputSchema: {
         code: z.string(),
         uid: z.string().optional(),
@@ -170,11 +170,11 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(qiweLoginVerify)
+    wrap(qiweiLoginVerify)
   );
 
   server.registerTool(
-    'qiwe_subscription_status',
+    'qiwei_subscription_status',
     {
       title: '查询企微包月订阅与席位状态',
       description: '查询企微包月订阅、到期时间、自动续费、已购/已用席位和飞马余额。首次登录或收到 402/403 时先调用。',
@@ -183,11 +183,11 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(qiweSubscriptionStatus)
+    wrap(qiweiSubscriptionStatus)
   );
 
   server.registerTool(
-    'qiwe_subscribe',
+    'qiwei_subscribe',
     {
       title: '开通、续费或增购企微席位',
       description: '使用飞马余额开通或续费企微包月订阅。1 个席位对应 1 个企业微信账号;seats 不传时默认 1。',
@@ -197,11 +197,11 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(qiweSubscribe)
+    wrap(qiweiSubscribe)
   );
 
   server.registerTool(
-    'qiwe_subscription_auto_renew',
+    'qiwei_subscription_auto_renew',
     {
       title: '设置企微订阅自动续费',
       description: '开启或关闭企微包月订阅到期自动续费。',
@@ -211,21 +211,21 @@ function createServer() {
         apiBase: z.string().optional()
       }
     },
-    wrap(qiweSubscriptionAutoRenew)
+    wrap(qiweiSubscriptionAutoRenew)
   );
 
   server.registerTool(
-    'qiwe_official_status',
+    'qiwei_official_status',
     {
       title: '检查企业微信官方 CLI 状态',
       description: '检查固定版本官方 CLI 是否已下载、版本是否正确,以及官方机器人扫码授权是否完成。此工具与 Fmode 网关登录状态完全独立。',
       inputSchema: {}
     },
-    wrap(qiweOfficialStatus)
+    wrap(qiweiOfficialStatus)
   );
 
   server.registerTool(
-    'qiwe_official_prepare',
+    'qiwei_official_prepare',
     {
       title: '下载企业微信官方 CLI',
       description: '将固定版本 @wecom/cli 下载到用户级缓存并校验。不会全局安装,不修改官方 CLI,也不会改动 Fmode 网关转发的企业微信接口通道。',
@@ -233,11 +233,11 @@ function createServer() {
         force: z.boolean().optional()
       }
     },
-    wrap(qiweOfficialPrepare)
+    wrap(qiweiOfficialPrepare)
   );
 
   server.registerTool(
-    'qiwe_official_help',
+    'qiwei_official_help',
     {
       title: '读取企业微信官方 CLI 帮助',
       description: '读取官方 CLI 总帮助、category 工具列表或具体 method 的参数 schema。category/method 级帮助需要先完成官方 CLI 初始化。',
@@ -246,11 +246,11 @@ function createServer() {
         method: z.string().regex(/^[a-z][a-z0-9_]*$/).optional()
       }
     },
-    wrap(qiweOfficialHelp)
+    wrap(qiweiOfficialHelp)
   );
 
   server.registerTool(
-    'qiwe_official_call',
+    'qiwei_official_call',
     {
       title: '调用企业微信官方 CLI 能力',
       description: '通过固定版本官方 CLI 调用通讯录、文档、会议、消息、日程或待办。参数以结构化 JSON 传递,不经过 shell,也不经过 Fmode 网关。',
@@ -261,7 +261,7 @@ function createServer() {
         timeoutMs: z.number().int().min(1000).max(300000).optional()
       }
     },
-    wrap(qiweOfficialCall)
+    wrap(qiweiOfficialCall)
   );
 
   return server;

+ 34 - 34
claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwe-api-catalog-run.js → claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwei-api-catalog-run.js

@@ -6,7 +6,7 @@ const {
   searchEndpoints,
   summarizeEndpoint
 } = require('../core/api-catalog');
-const { readQiweAuthToken, ensureQiweUid, readQiweApiBase } = require('../core/credentials');
+const { readQiweiAuthToken, ensureQiweiUid, readQiweiApiBase } = require('../core/credentials');
 const { callFmodeWecomGateway, redactSecret } = require('../providers/fmode-wecom-gateway');
 const { okResult, errorResult } = require('../core/result-envelope');
 
@@ -18,7 +18,7 @@ function buildCallTemplate(endpoint) {
   return args;
 }
 
-async function searchQiweApis(input = {}) {
+async function searchQiweiApis(input = {}) {
   const catalog = loadCatalog();
   const query = input.query || input.q || input.keyword || '';
   const matches = (query || input.module || input.tag)
@@ -37,8 +37,8 @@ async function searchQiweApis(input = {}) {
         `- ${item.id} | ${item.title}(method: ${item.method})必填参数: ${item.requiredParams.join(', ') || '无'}`
     ),
     '',
-    '下一步:用 qiwe_api_doc 读取某个接口的详细参数文档,再用 qiwe_api_call 传参调用。',
-    '清单里没有的接口,可直接用 qiwe_api_call 传 rawMethod + params 调用。'
+    '下一步:用 qiwei_api_doc 读取某个接口的详细参数文档,再用 qiwei_api_call 传参调用。',
+    '清单里没有的接口,可直接用 qiwei_api_call 传 rawMethod + params 调用。'
   ];
   return okResult({
     assistantMessage: lines.join('\n'),
@@ -68,15 +68,15 @@ function publicParams(params) {
     }));
 }
 
-async function getQiweApiDoc(input = {}) {
+async function getQiweiApiDoc(input = {}) {
   const idOrMethod = input.id || input.endpointId || input.method || input.path;
   if (!idOrMethod) {
-    return errorResult('请提供接口 id 或 method 路径(可先用 qiwe_api_search 查清单)。');
+    return errorResult('请提供接口 id 或 method 路径(可先用 qiwei_api_search 查清单)。');
   }
   const endpoint = findEndpoint(idOrMethod);
   if (!endpoint) {
     return errorResult(
-      `清单里没有找到接口「${idOrMethod}」。可用 qiwe_api_search 查询,或直接用 qiwe_api_call 传 rawMethod 调用未登记接口。`
+      `清单里没有找到接口「${idOrMethod}」。可用 qiwei_api_search 查询,或直接用 qiwei_api_call 传 rawMethod 调用未登记接口。`
     );
   }
   const template = buildCallTemplate(endpoint);
@@ -85,15 +85,15 @@ async function getQiweApiDoc(input = {}) {
     '',
     redactSecret(endpoint.summary || ''),
     '',
-    `method: ${endpoint.method || '(官方文档未公布,暂不可通过 qiwe_api_call 直接调用)'}(分类:${(endpoint.category || []).join(' > ') || '无'})`,
-    endpoint.apiPath ? `注意:本接口走 ${endpoint.apiPath}${endpoint.contentType ? `(${endpoint.contentType})` : ''};Fmode 网关当前尚未支持 multipart 文件传输,qiwe_api_call 暂不可调用。` : '',
+    `method: ${endpoint.method || '(官方文档未公布,暂不可通过 qiwei_api_call 直接调用)'}(分类:${(endpoint.category || []).join(' > ') || '无'})`,
+    endpoint.apiPath ? `注意:本接口走 ${endpoint.apiPath}${endpoint.contentType ? `(${endpoint.contentType})` : ''};Fmode 网关当前尚未支持 multipart 文件传输,qiwei_api_call 暂不可调用。` : '',
     '',
     '参数:',
     ...renderParamLines(endpoint.params),
     '',
     endpoint.responseHint ? `返回:${redactSecret(endpoint.responseHint)}` : '',
     '',
-    '调用示例(传给 qiwe_api_call):',
+    '调用示例(传给 qiwei_api_call):',
     '```json',
     JSON.stringify(template, null, 2),
     '```'
@@ -122,29 +122,29 @@ async function getQiweApiDoc(input = {}) {
 
 const BLOCKED_METHOD_PREFIXES = ['/login/', '/client/'];
 
-async function callQiweApi(input = {}) {
+async function callQiweiApi(input = {}) {
   const idOrMethod = input.id || input.endpointId;
   const rawMethod = input.rawMethod || input.method || (idOrMethod ? null : input.path);
   const endpoint = (idOrMethod || rawMethod) ? findEndpoint(idOrMethod || rawMethod) : undefined;
 
   if (!endpoint && !rawMethod) {
-    return errorResult('请提供接口 id(先用 qiwe_api_search 查清单),或提供 rawMethod 调用未登记接口。');
+    return errorResult('请提供接口 id(先用 qiwei_api_search 查清单),或提供 rawMethod 调用未登记接口。');
   }
 
-  const token = readQiweAuthToken(input);
+  const token = readQiweiAuthToken(input);
   if (!token) {
     return {
       status: 'needs_auth',
       assistantMessage: [
         '还没有找到 Fmode 鉴权 token,无法调用由 Fmode 网关转发的企业微信接口。',
         '',
-        '优先使用 Claude Code 已配置的 Fmode NewAPI sk- token;也可配置 QIWE_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken。',
-        '配置鉴权后先调用 qiwe_subscription_status,再用 qiwe_login_start 完成企微扫码登录。'
+        '优先使用 Claude Code 已配置的 Fmode NewAPI sk- token;也可配置 QIWEI_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken。',
+        '配置鉴权后先调用 qiwei_subscription_status,再用 qiwei_login_start 完成企微扫码登录。'
       ].join('\n'),
       summary: { errorKind: 'missing_auth_token', recoverable: true },
       data: {},
       files: [],
-      nextActions: ['配置 Fmode 鉴权 token', '调用 qiwe_subscription_status 查看订阅状态'],
+      nextActions: ['配置 Fmode 鉴权 token', '调用 qiwei_subscription_status 查看订阅状态'],
       warnings: [],
       errors: []
     };
@@ -152,7 +152,7 @@ async function callQiweApi(input = {}) {
 
   const method = endpoint ? endpoint.method : rawMethod;
   if (BLOCKED_METHOD_PREFIXES.some(prefix => String(method || '').startsWith(prefix))) {
-    return errorResult('登录和设备类接口必须使用 qiwe_login_* 专用工具,不能通过 qiwe_api_call 透传。');
+    return errorResult('登录和设备类接口必须使用 qiwei_login_* 专用工具,不能通过 qiwei_api_call 透传。');
   }
   const params = input.params && typeof input.params === 'object' ? { ...input.params } : {};
 
@@ -165,7 +165,7 @@ async function callQiweApi(input = {}) {
     }
     if (endpoint.apiPath && endpoint.apiPath !== '/api/qw/doApi') {
       return errorResult(
-        `接口 ${endpoint.id} 走 ${endpoint.apiPath}(${endpoint.contentType || 'multipart'}),Fmode 网关当前尚未支持文件流,qiwe_api_call 暂不可调用。`
+        `接口 ${endpoint.id} 走 ${endpoint.apiPath}(${endpoint.contentType || 'multipart'}),Fmode 网关当前尚未支持文件流,qiwei_api_call 暂不可调用。`
       );
     }
     const topMissing = (endpoint.params || [])
@@ -179,19 +179,19 @@ async function callQiweApi(input = {}) {
     if (topMissing.length) {
       return {
         status: 'needs_input',
-        assistantMessage: `缺少必填参数:${topMissing.join(', ')}。请用 qiwe_api_doc 查看参数说明后补齐再调用。`,
+        assistantMessage: `缺少必填参数:${topMissing.join(', ')}。请用 qiwei_api_doc 查看参数说明后补齐再调用。`,
         summary: { endpoint: endpoint.id, missing: topMissing },
         data: { endpoint: summarizeEndpoint(endpoint), missing: topMissing },
         files: [],
-        nextActions: ['用 qiwe_api_doc 核对必填参数', '补齐参数后重新调用'],
+        nextActions: ['用 qiwei_api_doc 核对必填参数', '补齐参数后重新调用'],
         warnings: [],
         errors: [{ message: `缺少必填参数:${topMissing.join(', ')}`, kind: 'request' }]
       };
     }
   }
 
-  const apiBase = readQiweApiBase(input);
-  const uid = ensureQiweUid(input);
+  const apiBase = readQiweiApiBase(input);
+  const uid = ensureQiweiUid(input);
   try {
     const result = await callFmodeWecomGateway({
       gatewayPath: '/doApi',
@@ -212,7 +212,7 @@ async function callQiweApi(input = {}) {
     if (kind === 'auth') {
       return {
         status: 'needs_auth',
-        assistantMessage: 'Fmode 鉴权 token 无效或已失效,请更新 QIWE_AUTH_TOKEN/FMODE_API_KEY 或重新登录平台后重试。',
+        assistantMessage: 'Fmode 鉴权 token 无效或已失效,请更新 QIWEI_AUTH_TOKEN/FMODE_API_KEY 或重新登录平台后重试。',
         summary: { endpoint: endpoint ? endpoint.id : method, errorKind: kind, httpStatus, recoverable: true },
         data: {},
         files: [],
@@ -224,11 +224,11 @@ async function callQiweApi(input = {}) {
     if (kind === 'device') {
       return {
         status: 'needs_login',
-        assistantMessage: `接口 ${method} 返回登录态错误(${safeMessage})。请先用 qiwe_login_status 检查,再用 qiwe_login_start 重新扫码登录。`,
+        assistantMessage: `接口 ${method} 返回登录态错误(${safeMessage})。请先用 qiwei_login_status 检查,再用 qiwei_login_start 重新扫码登录。`,
         summary: { endpoint: endpoint ? endpoint.id : method, errorKind: kind, httpStatus, recoverable: true },
         data: { message: safeMessage },
         files: [],
-        nextActions: ['调用 qiwe_login_status 检查设备在线状态', '掉线则调用 qiwe_login_start 重新登录'],
+        nextActions: ['调用 qiwei_login_status 检查设备在线状态', '掉线则调用 qiwei_login_start 重新登录'],
         warnings: [],
         errors: [{ message: safeMessage, kind, httpStatus }]
       };
@@ -236,11 +236,11 @@ async function callQiweApi(input = {}) {
     if (kind === 'billing') {
       return {
         status: 'needs_subscription',
-        assistantMessage: `企微订阅或飞马余额不足(${safeMessage})。请先调用 qiwe_subscription_status,必要时用 qiwe_subscribe 开通或续费。`,
+        assistantMessage: `企微订阅或飞马余额不足(${safeMessage})。请先调用 qiwei_subscription_status,必要时用 qiwei_subscribe 开通或续费。`,
         summary: { endpoint: endpoint ? endpoint.id : method, errorKind: kind, httpStatus },
         data: { message: safeMessage },
         files: [],
-        nextActions: ['调用 qiwe_subscription_status', '调用 qiwe_subscribe 开通或续费'],
+        nextActions: ['调用 qiwei_subscription_status', '调用 qiwei_subscribe 开通或续费'],
         warnings: [],
         errors: []
       };
@@ -248,11 +248,11 @@ async function callQiweApi(input = {}) {
     if (kind === 'permission') {
       return {
         status: 'needs_seat',
-        assistantMessage: `企微席位或权限不足(${safeMessage})。请调用 qiwe_subscription_status 检查并按需增购席位。`,
+        assistantMessage: `企微席位或权限不足(${safeMessage})。请调用 qiwei_subscription_status 检查并按需增购席位。`,
         summary: { endpoint: endpoint ? endpoint.id : method, errorKind: kind, httpStatus },
         data: { message: safeMessage },
         files: [],
-        nextActions: ['调用 qiwe_subscription_status', '调用 qiwe_subscribe 增购席位'],
+        nextActions: ['调用 qiwei_subscription_status', '调用 qiwei_subscribe 增购席位'],
         warnings: [],
         errors: []
       };
@@ -260,11 +260,11 @@ async function callQiweApi(input = {}) {
     if (kind === 'request') {
       return {
         status: 'needs_input',
-        assistantMessage: `接口 ${method} 返回入参错误(${safeMessage})。请用 qiwe_api_doc 核对参数后重试。`,
+        assistantMessage: `接口 ${method} 返回入参错误(${safeMessage})。请用 qiwei_api_doc 核对参数后重试。`,
         summary: { endpoint: endpoint ? endpoint.id : method, errorKind: kind, httpStatus },
         data: { message: safeMessage },
         files: [],
-        nextActions: ['用 qiwe_api_doc 核对参数', '修正参数后重新调用'],
+        nextActions: ['用 qiwei_api_doc 核对参数', '修正参数后重新调用'],
         warnings: [],
         errors: [{ message: safeMessage, kind, httpStatus }]
       };
@@ -283,7 +283,7 @@ async function callQiweApi(input = {}) {
 }
 
 module.exports = {
-  searchQiweApis,
-  getQiweApiDoc,
-  callQiweApi
+  searchQiweiApis,
+  getQiweiApiDoc,
+  callQiweiApi
 };

+ 58 - 58
claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwe-login-run.js → claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwei-login-run.js

@@ -2,15 +2,15 @@ const fs = require('fs');
 const path = require('path');
 const { spawn } = require('child_process');
 const {
-  readQiweAuthToken,
-  ensureQiweUid,
-  readQiweApiBase,
-  saveQiweClientConfig
+  readQiweiAuthToken,
+  ensureQiweiUid,
+  readQiweiApiBase,
+  saveQiweiClientConfig
 } = require('../core/credentials');
 const { callFmodeWecomGateway, redactSecret } = require('../providers/fmode-wecom-gateway');
 const { okResult, errorResult } = require('../core/result-envelope');
 const { latestPath } = require('../core/output-paths');
-const { saveSubscribePage, QIWE_MONTHLY_PRICE } = require('../core/subscribe-page');
+const { saveSubscribePage, QIWEI_MONTHLY_PRICE } = require('../core/subscribe-page');
 const { startLoginFlowServer } = require('../core/login-flow-server');
 
 const QR_STATUS = {
@@ -27,7 +27,7 @@ function authRequiredResult() {
     status: 'needs_auth',
     assistantMessage: [
       '还没有找到 Fmode 鉴权 token,无法调用由 Fmode 网关转发的企业微信接口。',
-      '优先复用 Claude Code 已配置的 Fmode NewAPI sk- token;也可配置 QIWE_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken。',
+      '优先复用 Claude Code 已配置的 Fmode NewAPI sk- token;也可配置 QIWEI_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken。',
       '企业微信接口访问凭据和设备上下文由 Fmode 网关管理,技能不会读取或保存。'
     ].join('\n'),
     summary: { configured: false, errorKind: 'missing_auth_token', recoverable: true },
@@ -41,7 +41,7 @@ function authRequiredResult() {
 
 function saveQrcodeImage(base64Data) {
   try {
-    const filePath = latestPath('login', 'qiwe-login-qrcode.png');
+    const filePath = latestPath('login', 'qiwei-login-qrcode.png');
     const data = String(base64Data || '').replace(/^data:image\/\w+;base64,/, '');
     fs.writeFileSync(filePath, Buffer.from(data, 'base64'));
     return filePath;
@@ -52,7 +52,7 @@ function saveQrcodeImage(base64Data) {
 
 function saveQrcodePreviewHtml(qrcodeImagePath) {
   try {
-    const htmlPath = latestPath('login', 'qiwe-login-qrcode.html');
+    const htmlPath = latestPath('login', 'qiwei-login-qrcode.html');
     const html = [
       '<!DOCTYPE html>',
       '<html lang="zh-CN">',
@@ -108,11 +108,11 @@ function gatewayErrorResult(error, stage, fallbackMessage) {
   if (kind === 'billing') {
     return {
       status: 'needs_subscription',
-      assistantMessage: `企微订阅未开通、已到期或飞马余额不足(${safeMessage})。请先调用 qiwe_subscription_status,必要时用 qiwe_subscribe 开通或续费。`,
+      assistantMessage: `企微订阅未开通、已到期或飞马余额不足(${safeMessage})。请先调用 qiwei_subscription_status,必要时用 qiwei_subscribe 开通或续费。`,
       summary: { stage, errorKind: kind, recoverable: true },
       data: {},
       files: [],
-      nextActions: ['调用 qiwe_subscription_status', '调用 qiwe_subscribe 开通或续费'],
+      nextActions: ['调用 qiwei_subscription_status', '调用 qiwei_subscribe 开通或续费'],
       warnings: [],
       errors: []
     };
@@ -124,7 +124,7 @@ function gatewayErrorResult(error, stage, fallbackMessage) {
       summary: { stage, errorKind: kind, recoverable: true },
       data: {},
       files: [],
-      nextActions: ['调用 qiwe_subscription_status', '调用 qiwe_subscribe 增购席位'],
+      nextActions: ['调用 qiwei_subscription_status', '调用 qiwei_subscribe 增购席位'],
       warnings: [],
       errors: []
     };
@@ -135,11 +135,11 @@ function gatewayErrorResult(error, stage, fallbackMessage) {
   });
 }
 
-async function qiweLoginStatus(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiLoginStatus(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
-  const uid = ensureQiweUid(input);
-  const apiBase = readQiweApiBase(input);
+  const uid = ensureQiweiUid(input);
+  const apiBase = readQiweiApiBase(input);
   try {
     const result = await callFmodeWecomGateway({
       gatewayPath: '/login/status',
@@ -154,8 +154,8 @@ async function qiweLoginStatus(input = {}) {
       assistantMessage: data.configured
         ? online
           ? `企业微信设备在线(uid: ${uid}),可以直接调用业务接口。`
-          : `企业微信设备当前不在线(状态 ${data.statusCode ?? '未知'}),请调用 qiwe_login_start 重新登录。`
-        : `当前 uid(${uid})尚未配置企业微信设备,请调用 qiwe_login_start 开始扫码登录。`,
+          : `企业微信设备当前不在线(状态 ${data.statusCode ?? '未知'}),请调用 qiwei_login_start 重新登录。`
+        : `当前 uid(${uid})尚未配置企业微信设备,请调用 qiwei_login_start 开始扫码登录。`,
       summary: {
         configured: Boolean(data.configured),
         online,
@@ -169,7 +169,7 @@ async function qiweLoginStatus(input = {}) {
         statusCode: data.statusCode ?? null,
         detail: data.detail
       },
-      nextActions: online ? [] : ['调用 qiwe_login_start 开始扫码登录']
+      nextActions: online ? [] : ['调用 qiwei_login_start 开始扫码登录']
     });
   } catch (error) {
     return gatewayErrorResult(error, 'loginStatus', '查询登录状态失败');
@@ -182,30 +182,30 @@ function subscriptionRequiredResult({ apiBase, token, uid, detail }) {
   return {
     status: 'needs_subscription',
     assistantMessage: [
-      `登录前需要先开通企微包月订阅:每个账号 ¥${QIWE_MONTHLY_PRICE}/月,从飞马余额扣费。`,
+      `登录前需要先开通企微包月订阅:每个账号 ¥${QIWEI_MONTHLY_PRICE}/月,从飞马余额扣费。`,
       opened
         ? `已自动打开套餐选择页:${pagePath},请在页面上选择席位与时长并点击「开通服务」。`
         : pagePath
           ? `请打开套餐选择页完成开通:${pagePath}`
-          : '套餐页生成失败,可直接调用 qiwe_subscribe 开通。',
-      '开通成功后重新调用 qiwe_login_start 进入扫码登录(第 2 步)。',
+          : '套餐页生成失败,可直接调用 qiwei_subscribe 开通。',
+      '开通成功后重新调用 qiwei_login_start 进入扫码登录(第 2 步)。',
       detail ? `订阅状态:${detail}` : ''
     ].filter(Boolean).join('\n'),
-    summary: { stage: 'loginStart', errorKind: 'subscription_required', uid, subscribePage: pagePath || null, browserOpened: opened, monthlyPrice: QIWE_MONTHLY_PRICE },
+    summary: { stage: 'loginStart', errorKind: 'subscription_required', uid, subscribePage: pagePath || null, browserOpened: opened, monthlyPrice: QIWEI_MONTHLY_PRICE },
     data: { uid, subscribePage: pagePath || null },
     files: pagePath ? [pagePath] : [],
-    nextActions: ['在套餐页开通订阅(或调用 qiwe_subscribe)', '开通后重新调用 qiwe_login_start'],
+    nextActions: ['在套餐页开通订阅(或调用 qiwei_subscribe)', '开通后重新调用 qiwei_login_start'],
     warnings: [],
     errors: []
   };
 }
 
-async function qiweLoginStart(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiLoginStart(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
-  const uid = ensureQiweUid(input);
-  const apiBase = readQiweApiBase(input);
-  if (input.persistConfig !== false) saveQiweClientConfig({ uid, apiBase });
+  const uid = ensureQiweiUid(input);
+  const apiBase = readQiweiApiBase(input);
+  if (input.persistConfig !== false) saveQiweiClientConfig({ uid, apiBase });
   if (input.flowUi !== false) {
     try {
       const { url } = await startLoginFlowServer({
@@ -215,7 +215,7 @@ async function qiweLoginStart(input = {}) {
         port: input.flowPort,
         onQrcode: (buffer) => {
           try {
-            fs.writeFileSync(latestPath('login', 'qiwe-login-qrcode.png'), buffer);
+            fs.writeFileSync(latestPath('login', 'qiwei-login-qrcode.png'), buffer);
           } catch {}
         }
       });
@@ -223,12 +223,12 @@ async function qiweLoginStart(input = {}) {
       return okResult({
         assistantMessage: [
           `已启动企微登录流程页服务:${url}${opened ? '(已自动打开浏览器)' : ''}。`,
-          `页面会自动完成两步流程:未开通订阅时先展示套餐页(每号 ¥${QIWE_MONTHLY_PRICE}/月,飞马余额扣费),开通后自动生成二维码并每 3 秒自动检测扫码状态,需要验证码时页面会提示输入。`,
-          '也可以用 qiwe_login_check 在对话中轮询登录状态。'
+          `页面会自动完成两步流程:未开通订阅时先展示套餐页(每号 ¥${QIWEI_MONTHLY_PRICE}/月,飞马余额扣费),开通后自动生成二维码并每 3 秒自动检测扫码状态,需要验证码时页面会提示输入。`,
+          '也可以用 qiwei_login_check 在对话中轮询登录状态。'
         ].join('\n'),
-        summary: { uid, flowUrl: url, browserOpened: Boolean(opened), monthlyPrice: QIWE_MONTHLY_PRICE },
+        summary: { uid, flowUrl: url, browserOpened: Boolean(opened), monthlyPrice: QIWEI_MONTHLY_PRICE },
         data: { uid, flowUrl: url, browserOpened: Boolean(opened) },
-        nextActions: ['引导用户在流程页完成开通与扫码', '用 qiwe_login_check 轮询登录状态']
+        nextActions: ['引导用户在流程页完成开通与扫码', '用 qiwei_login_check 轮询登录状态']
       });
     } catch (error) {
       // 流程页服务启动失败时回退到静态二维码流程
@@ -273,8 +273,8 @@ async function qiweLoginStart(input = {}) {
           : filePath
             ? `请打开或展示二维码图片:${filePath}`
             : 'Fmode 网关未返回可保存的二维码图片,请重试。',
-        '用户扫码并在手机上确认后,调用 qiwe_login_check 轮询状态。',
-        '如果返回状态 10,请向用户索要 6 位验证码并调用 qiwe_login_verify。'
+        '用户扫码并在手机上确认后,调用 qiwei_login_check 轮询状态。',
+        '如果返回状态 10,请向用户索要 6 位验证码并调用 qiwei_login_verify。'
       ].join('\n'),
       summary: {
         uid,
@@ -290,7 +290,7 @@ async function qiweLoginStart(input = {}) {
         loginQrcodeBase64Data: filePath ? undefined : data.loginQrcodeBase64Data
       },
       files: [filePath, htmlPath].filter(Boolean),
-      nextActions: ['确认用户看到二维码并扫码', '调用 qiwe_login_check 轮询登录状态']
+      nextActions: ['确认用户看到二维码并扫码', '调用 qiwei_login_check 轮询登录状态']
     });
   } catch (error) {
     if (error && (error.kind === 'billing' || error.httpStatus === 402)) {
@@ -300,11 +300,11 @@ async function qiweLoginStart(input = {}) {
   }
 }
 
-async function qiweLoginCheck(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiLoginCheck(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
-  const uid = ensureQiweUid(input);
-  const apiBase = readQiweApiBase(input);
+  const uid = ensureQiweiUid(input);
+  const apiBase = readQiweiApiBase(input);
   try {
     const result = await callFmodeWecomGateway({
       gatewayPath: '/login/check',
@@ -333,11 +333,11 @@ async function qiweLoginCheck(input = {}) {
     if (statusCode === '10') {
       return {
         status: 'needs_verify_code',
-        assistantMessage: '用户已在手机上确认,但需要输入手机端显示的 6 位验证码。请取得验证码后调用 qiwe_login_verify。',
+        assistantMessage: '用户已在手机上确认,但需要输入手机端显示的 6 位验证码。请取得验证码后调用 qiwei_login_verify。',
         summary: { loggedIn: false, uid, statusCode: 10 },
         data: { uid, status: detail },
         files: [],
-        nextActions: ['向用户索要 6 位验证码', '调用 qiwe_login_verify'],
+        nextActions: ['向用户索要 6 位验证码', '调用 qiwei_login_verify'],
         warnings: [],
         errors: []
       };
@@ -345,34 +345,34 @@ async function qiweLoginCheck(input = {}) {
     return okResult({
       assistantMessage: `当前扫码状态:${statusCode}(${statusText})。${
         ['-1', '1'].includes(statusCode)
-          ? '请继续等待用户扫码/确认,3-5 秒后再次调用 qiwe_login_check。'
+          ? '请继续等待用户扫码/确认,3-5 秒后再次调用 qiwei_login_check。'
           : statusCode === '4'
-            ? '用户取消了登录,可重新调用 qiwe_login_start。'
+            ? '用户取消了登录,可重新调用 qiwei_login_start。'
             : statusCode === '0'
-              ? '可再次调用 qiwe_login_check 并传 manual=true 尝试免扫码登录。'
+              ? '可再次调用 qiwei_login_check 并传 manual=true 尝试免扫码登录。'
               : ''
       }`,
       summary: { loggedIn: false, uid, statusCode: Number(statusCode) },
       data: { uid, status: detail },
       nextActions:
         statusCode === '4'
-          ? ['重新调用 qiwe_login_start']
+          ? ['重新调用 qiwei_login_start']
           : statusCode === '0'
-            ? ['调用 qiwe_login_check,传 manual=true']
-            : ['稍后再次调用 qiwe_login_check']
+            ? ['调用 qiwei_login_check,传 manual=true']
+            : ['稍后再次调用 qiwei_login_check']
     });
   } catch (error) {
     return gatewayErrorResult(error, 'loginCheck', '检测扫码状态失败');
   }
 }
 
-async function qiweLoginVerify(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiLoginVerify(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
-  const uid = ensureQiweUid(input);
+  const uid = ensureQiweiUid(input);
   const code = String(input.code || '').trim();
   if (!/^\d{6}$/.test(code)) return errorResult('请提供手机端显示的 6 位数字验证码(入参 code)。');
-  const apiBase = readQiweApiBase(input);
+  const apiBase = readQiweiApiBase(input);
   try {
     await callFmodeWecomGateway({
       gatewayPath: '/login/verify',
@@ -381,9 +381,9 @@ async function qiweLoginVerify(input = {}) {
       apiBase
     });
     return okResult({
-      assistantMessage: '验证码已提交。请调用 qiwe_login_check 再次确认登录状态。',
+      assistantMessage: '验证码已提交。请调用 qiwei_login_check 再次确认登录状态。',
       summary: { verified: true, uid },
-      nextActions: ['调用 qiwe_login_check 确认登录状态']
+      nextActions: ['调用 qiwei_login_check 确认登录状态']
     });
   } catch (error) {
     return gatewayErrorResult(error, 'loginVerify', '验证码校验失败');
@@ -391,8 +391,8 @@ async function qiweLoginVerify(input = {}) {
 }
 
 module.exports = {
-  qiweLoginStatus,
-  qiweLoginStart,
-  qiweLoginCheck,
-  qiweLoginVerify
+  qiweiLoginStatus,
+  qiweiLoginStart,
+  qiweiLoginCheck,
+  qiweiLoginVerify
 };

+ 19 - 19
claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwe-subscription-run.js → claude-code/claude-code-qiwe-assistant/mcp/src/tools/qiwei-subscription-run.js

@@ -1,11 +1,11 @@
-const { readQiweAuthToken, readQiweApiBase } = require('../core/credentials');
+const { readQiweiAuthToken, readQiweiApiBase } = require('../core/credentials');
 const { callFmodeWecomGateway, redactSecret } = require('../providers/fmode-wecom-gateway');
 const { okResult, errorResult } = require('../core/result-envelope');
 
 function authRequiredResult() {
   return {
     status: 'needs_auth',
-    assistantMessage: '还没有找到 Fmode 鉴权 token,无法调用由 Fmode 网关转发的企业微信接口。请配置 QIWE_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken 后重试。',
+    assistantMessage: '还没有找到 Fmode 鉴权 token,无法调用由 Fmode 网关转发的企业微信接口。请配置 QIWEI_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken 后重试。',
     summary: { errorKind: 'missing_auth_token', recoverable: true },
     data: {},
     files: [],
@@ -22,11 +22,11 @@ function subscriptionError(error, stage) {
   if (kind === 'billing') {
     return {
       status: 'needs_recharge',
-      assistantMessage: `飞马余额不足或订阅扣费失败(${safeMessage})。请充值后重新调用 qiwe_subscribe。`,
+      assistantMessage: `飞马余额不足或订阅扣费失败(${safeMessage})。请充值后重新调用 qiwei_subscribe。`,
       summary: { stage, errorKind: kind, recoverable: true },
       data: {},
       files: [],
-      nextActions: ['充值飞马余额', '重新调用 qiwe_subscribe'],
+      nextActions: ['充值飞马余额', '重新调用 qiwei_subscribe'],
       warnings: [],
       errors: []
     };
@@ -37,21 +37,21 @@ function subscriptionError(error, stage) {
   });
 }
 
-async function qiweSubscriptionStatus(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiSubscriptionStatus(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
   try {
     const result = await callFmodeWecomGateway({
       gatewayPath: '/subscribe/status',
       httpMethod: 'GET',
       token,
-      apiBase: readQiweApiBase(input)
+      apiBase: readQiweiApiBase(input)
     });
     const data = result.data || {};
     return okResult({
       assistantMessage: data.subscribed
         ? `企微包月订阅有效:${data.usedSeats ?? 0}/${data.seats ?? 0} 个席位已使用,到期时间 ${data.expireAt || '未知'}。`
-        : '企微包月订阅尚未开通或已到期,请调用 qiwe_subscribe 开通或续费。',
+        : '企微包月订阅尚未开通或已到期,请调用 qiwei_subscribe 开通或续费。',
       summary: {
         subscribed: Boolean(data.subscribed),
         seats: data.seats ?? null,
@@ -61,15 +61,15 @@ async function qiweSubscriptionStatus(input = {}) {
         balance: data.balance
       },
       data,
-      nextActions: data.subscribed ? [] : ['调用 qiwe_subscribe 开通或续费']
+      nextActions: data.subscribed ? [] : ['调用 qiwei_subscribe 开通或续费']
     });
   } catch (error) {
     return subscriptionError(error, 'subscriptionStatus');
   }
 }
 
-async function qiweSubscribe(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiSubscribe(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
   const seats = input.seats === undefined ? undefined : Number(input.seats);
   if (seats !== undefined && (!Number.isInteger(seats) || seats < 1)) {
@@ -80,7 +80,7 @@ async function qiweSubscribe(input = {}) {
       gatewayPath: '/subscribe',
       body: seats === undefined ? {} : { seats },
       token,
-      apiBase: readQiweApiBase(input)
+      apiBase: readQiweiApiBase(input)
     });
     const data = result.data || {};
     return okResult({
@@ -92,15 +92,15 @@ async function qiweSubscribe(input = {}) {
         autoRenew: data.autoRenew ?? null
       },
       data,
-      nextActions: ['调用 qiwe_login_start 添加或重新登录企业微信账号']
+      nextActions: ['调用 qiwei_login_start 添加或重新登录企业微信账号']
     });
   } catch (error) {
     return subscriptionError(error, 'subscribe');
   }
 }
 
-async function qiweSubscriptionAutoRenew(input = {}) {
-  const token = readQiweAuthToken(input);
+async function qiweiSubscriptionAutoRenew(input = {}) {
+  const token = readQiweiAuthToken(input);
   if (!token) return authRequiredResult();
   if (typeof input.autoRenew !== 'boolean') return errorResult('autoRenew 必须是 boolean。');
   try {
@@ -108,7 +108,7 @@ async function qiweSubscriptionAutoRenew(input = {}) {
       gatewayPath: '/subscribe/autoRenew',
       body: { autoRenew: input.autoRenew },
       token,
-      apiBase: readQiweApiBase(input)
+      apiBase: readQiweiApiBase(input)
     });
     const data = result.data || {};
     return okResult({
@@ -122,7 +122,7 @@ async function qiweSubscriptionAutoRenew(input = {}) {
 }
 
 module.exports = {
-  qiweSubscriptionStatus,
-  qiweSubscribe,
-  qiweSubscriptionAutoRenew
+  qiweiSubscriptionStatus,
+  qiweiSubscribe,
+  qiweiSubscriptionAutoRenew
 };

+ 8 - 8
claude-code/claude-code-qiwe-assistant/mcp/src/tools/wecom-official-cli-run.js

@@ -1,13 +1,13 @@
 const {
-  qiweOfficialStatus,
-  qiweOfficialPrepare,
-  qiweOfficialHelp,
-  qiweOfficialCall
+  qiweiOfficialStatus,
+  qiweiOfficialPrepare,
+  qiweiOfficialHelp,
+  qiweiOfficialCall
 } = require('../providers/wecom-official-cli');
 
 module.exports = {
-  qiweOfficialStatus,
-  qiweOfficialPrepare,
-  qiweOfficialHelp,
-  qiweOfficialCall
+  qiweiOfficialStatus,
+  qiweiOfficialPrepare,
+  qiweiOfficialHelp,
+  qiweiOfficialCall
 };

+ 2 - 2
claude-code/claude-code-qiwe-assistant/package-lock.json

@@ -1,11 +1,11 @@
 {
-  "name": "claude-code-qiwe-assistant",
+  "name": "claude-code-qiwei-assistant",
   "version": "0.3.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "claude-code-qiwe-assistant",
+      "name": "claude-code-qiwei-assistant",
       "version": "0.3.0",
       "dependencies": {
         "@modelcontextprotocol/sdk": "^1.12.1",

+ 4 - 4
claude-code/claude-code-qiwe-assistant/package.json

@@ -1,16 +1,16 @@
 {
-  "name": "claude-code-qiwe-assistant",
+  "name": "claude-code-qiwei-assistant",
   "version": "0.3.0",
   "description": "企业微信技能包:保留 Fmode 网关转发的企业微信接口,并增加按需下载的官方 CLI 会议与文档能力",
   "main": "mcp/src/server.js",
   "scripts": {
-    "check": "node --check mcp/src/server.js && node --check mcp/src/core/api-catalog.js && node --check mcp/src/core/credentials.js && node --check mcp/src/core/result-envelope.js && node --check mcp/src/core/output-paths.js && node --check mcp/src/core/subscribe-page.js && node --check mcp/src/core/login-flow-server.js && node --check mcp/src/core/wecom-cli-runtime.js && node --check mcp/src/providers/fmode-wecom-gateway.js && node --check mcp/src/providers/wecom-official-cli.js && node --check mcp/src/tools/qiwe-api-catalog-run.js && node --check mcp/src/tools/qiwe-login-run.js && node --check mcp/src/tools/qiwe-subscription-run.js && node --check mcp/src/tools/wecom-official-cli-run.js && node --check bin/qiwe-official-cli.js && node --check scripts/install-wecom-cli.js && node --check scripts/smoke-test.js && node --check scripts/wecom-cli-smoke-test.js && node --check scripts/validate-output-standard.js",
+    "check": "node --check mcp/src/server.js && node --check mcp/src/core/api-catalog.js && node --check mcp/src/core/credentials.js && node --check mcp/src/core/result-envelope.js && node --check mcp/src/core/output-paths.js && node --check mcp/src/core/subscribe-page.js && node --check mcp/src/core/login-flow-server.js && node --check mcp/src/core/wecom-cli-runtime.js && node --check mcp/src/providers/fmode-wecom-gateway.js && node --check mcp/src/providers/wecom-official-cli.js && node --check mcp/src/tools/qiwei-api-catalog-run.js && node --check mcp/src/tools/qiwei-login-run.js && node --check mcp/src/tools/qiwei-subscription-run.js && node --check mcp/src/tools/wecom-official-cli-run.js && node --check bin/qiwei-official-cli.js && node --check scripts/install-wecom-cli.js && node --check scripts/smoke-test.js && node --check scripts/wecom-cli-smoke-test.js && node --check scripts/validate-output-standard.js",
     "start": "node mcp/src/server.js",
     "smoke": "node scripts/smoke-test.js && node scripts/wecom-cli-smoke-test.js",
     "outputs:validate": "node scripts/validate-output-standard.js",
     "wecom:install": "node scripts/install-wecom-cli.js",
-    "wecom:status": "node bin/qiwe-official-cli.js status",
-    "wecom:init": "node bin/qiwe-official-cli.js init"
+    "wecom:status": "node bin/qiwei-official-cli.js status",
+    "wecom:init": "node bin/qiwei-official-cli.js init"
   },
   "dependencies": {
     "@modelcontextprotocol/sdk": "^1.12.1",

+ 7 - 7
claude-code/claude-code-qiwe-assistant/scripts/build-catalog.py

@@ -2,10 +2,10 @@
 """Build the enterprise WeChat endpoint catalog from markdown OpenAPI docs."""
 import re, yaml, json, html, os, sys, glob
 
-DOCS = sys.argv[1] if len(sys.argv) > 1 else os.environ.get('QIWE_DOCS_DIR')
-OUT = sys.argv[2] if len(sys.argv) > 2 else os.path.join(os.path.dirname(__file__), '..', 'mcp', 'catalog', 'qiwe-endpoints.json')
+DOCS = sys.argv[1] if len(sys.argv) > 1 else os.environ.get('QIWEI_DOCS_DIR')
+OUT = sys.argv[2] if len(sys.argv) > 2 else os.path.join(os.path.dirname(__file__), '..', 'mcp', 'catalog', 'qiwei-endpoints.json')
 if not DOCS:
-    raise SystemExit('请通过第一个参数或 QIWE_DOCS_DIR 指定接口文档目录')
+    raise SystemExit('请通过第一个参数或 QIWEI_DOCS_DIR 指定接口文档目录')
 
 def strip_html(s):
     if not s: return ''
@@ -55,7 +55,7 @@ for path in sorted(glob.glob(os.path.join(DOCS, '*.md'))):
     try:
         spec = yaml.safe_load(m.group(1))
         paths = spec['paths']
-        api_path = next(p for p in ('/api/qw/doApi', '/qiwe/api/qw/doApi', '/api/qw/doFileApi') if p in paths)
+        api_path = next(p for p in ('/api/qw/doApi', '/qiwei/api/qw/doApi', '/api/qw/doFileApi') if p in paths)
         post = paths[api_path]['post']
     except Exception:
         skipped.append(fname); continue
@@ -111,7 +111,7 @@ MANUAL_ENDPOINTS = [
         'id': 'cloud.bigFileDownloadAsync',
         'module': 'cloud',
         'title': '企微大文件异步下载',
-        'summary': '文件大于 20M 使用此接口;下载地址为临时云资源(7–15 天不定期清理);异步结果通过回调通知,根据 requestId 匹配。当前未登记 method 路由值,qiwe_api_call 暂不可调用。',
+        'summary': '文件大于 20M 使用此接口;下载地址为临时云资源(7–15 天不定期清理);异步结果通过回调通知,根据 requestId 匹配。当前未登记 method 路由值,qiwei_api_call 暂不可调用。',
         'method': None,
         'category': ['API参考', '媒体与运营', '文件与媒体(下载/上传)'],
         'docFile': '企微大文件异步下载.md',
@@ -141,11 +141,11 @@ catalog = {
     'updatedAt': '2026-07-10',
     'source': '企业微信开放接口资料',
     'gateway': {
-        'baseUrl': 'https://server.fmode.cn/api/qiwe',
+        'baseUrl': 'https://server.fmode.cn/api/qiwei',
         'endpoint': 'POST {baseUrl}/doApi',
         'auth': 'Authorization: Bearer <Fmode token>',
         'envelope': 'Fmode 网关请求信封:{"uid": "<本地稳定设备标识>", "method": "<接口 method 路径>", "params": {...}};企业微信接口访问凭据和设备上下文由 Fmode 网关管理。',
-        'note': '所有业务请求均通过 Fmode 网关转发的企业微信接口执行。清单未登记的普通 JSON 接口可用 qiwe_api_call 传 rawMethod + params;登录和设备接口必须使用 qiwe_login_* 专用工具。'
+        'note': '所有业务请求均通过 Fmode 网关转发的企业微信接口执行。清单未登记的普通 JSON 接口可用 qiwei_api_call 传 rawMethod + params;登录和设备接口必须使用 qiwei_login_* 专用工具。'
     },
     'modules': sorted({ep['module'] for ep in endpoints}),
     'endpoints': endpoints,

+ 25 - 25
claude-code/claude-code-qiwe-assistant/scripts/smoke-test.js

@@ -2,18 +2,18 @@
 const assert = require('assert');
 const http = require('http');
 const { loadCatalog, searchEndpoints, findEndpoint } = require('../mcp/src/core/api-catalog');
-const { searchQiweApis, getQiweApiDoc, callQiweApi } = require('../mcp/src/tools/qiwe-api-catalog-run');
+const { searchQiweiApis, getQiweiApiDoc, callQiweiApi } = require('../mcp/src/tools/qiwei-api-catalog-run');
 const {
-  qiweLoginStatus,
-  qiweLoginStart,
-  qiweLoginCheck,
-  qiweLoginVerify
-} = require('../mcp/src/tools/qiwe-login-run');
+  qiweiLoginStatus,
+  qiweiLoginStart,
+  qiweiLoginCheck,
+  qiweiLoginVerify
+} = require('../mcp/src/tools/qiwei-login-run');
 const {
-  qiweSubscriptionStatus,
-  qiweSubscribe,
-  qiweSubscriptionAutoRenew
-} = require('../mcp/src/tools/qiwe-subscription-run');
+  qiweiSubscriptionStatus,
+  qiweiSubscribe,
+  qiweiSubscriptionAutoRenew
+} = require('../mcp/src/tools/qiwei-subscription-run');
 
 const PRIVATE_PROVIDER_NAME = ['private', 'provider'].join('-');
 const PRIVATE_PROVIDER_HOST = ['private', 'provider', 'invalid'].join('.');
@@ -106,7 +106,7 @@ async function startMockGateway() {
 async function main() {
   const catalog = loadCatalog();
   assert(catalog.endpoints.length >= 100, 'catalog should contain 100+ endpoints');
-  assert(catalog.gateway.baseUrl.includes('server.fmode.cn/api/qiwe'));
+  assert(catalog.gateway.baseUrl.includes('server.fmode.cn/api/qiwei'));
   assert(catalog.endpoints.every(endpoint => !Object.hasOwn(endpoint, 'docUrl')));
   assertProviderHidden(catalog);
   console.log(`[ok] catalog loaded: ${catalog.endpoints.length} endpoints`);
@@ -114,10 +114,10 @@ async function main() {
   const hits = searchEndpoints({ query: '发送 文本' });
   assert(hits.length > 0 && hits[0].module === 'msg');
   assert(findEndpoint('/login/getLoginQrcode'));
-  const searchResult = await searchQiweApis({ query: '朋友圈' });
+  const searchResult = await searchQiweiApis({ query: '朋友圈' });
   assert(searchResult.status === 'ok' && searchResult.data.endpoints.length > 0);
   assertProviderHidden(searchResult);
-  const doc = await getQiweApiDoc({ id: 'user.getProfile' });
+  const doc = await getQiweiApiDoc({ id: 'user.getProfile' });
   assert(doc.status === 'ok' && doc.data.callTemplate);
   assertProviderHidden(doc);
   assert(!JSON.stringify(doc).includes('"guid"'));
@@ -126,52 +126,52 @@ async function main() {
   const gateway = await startMockGateway();
   const common = {
     authToken: 'sk-test-relay-token',
-    uid: 'qiwe-smoke-device',
+    uid: 'qiwei-smoke-device',
     apiBase: gateway.apiBase,
     persistConfig: false
   };
   try {
-    const call = await callQiweApi({ ...common, id: 'user.getProfile', params: {} });
+    const call = await callQiweiApi({ ...common, id: 'user.getProfile', params: {} });
     assert.strictEqual(call.status, 'ok');
     const apiRequest = gateway.requests.at(-1);
     assert.strictEqual(apiRequest.path, '/doApi');
     assert.strictEqual(apiRequest.authorization, 'Bearer sk-test-relay-token');
     assert.deepStrictEqual(apiRequest.body, {
-      uid: 'qiwe-smoke-device',
+      uid: 'qiwei-smoke-device',
       method: '/user/getProfile',
       params: {}
     });
     assert(!JSON.stringify(apiRequest.body).includes('tokenId'));
     assert(!JSON.stringify(apiRequest.body).includes('guid'));
     assertProviderHidden(call);
-    console.log('[ok] qiwe_api_call uses the Fmode gateway envelope without private credentials');
+    console.log('[ok] qiwei_api_call uses the Fmode gateway envelope without private credentials');
 
-    const blocked = await callQiweApi({ ...common, rawMethod: '/login/manualLogin', params: {} });
+    const blocked = await callQiweiApi({ ...common, rawMethod: '/login/manualLogin', params: {} });
     assert.strictEqual(blocked.status, 'error');
     assert.strictEqual(gateway.requests.at(-1), apiRequest);
 
-    const subscription = await qiweSubscriptionStatus(common);
+    const subscription = await qiweiSubscriptionStatus(common);
     assert.strictEqual(subscription.status, 'ok');
     assert.strictEqual(subscription.summary.subscribed, true);
-    const subscribed = await qiweSubscribe({ ...common, seats: 3 });
+    const subscribed = await qiweiSubscribe({ ...common, seats: 3 });
     assert.strictEqual(subscribed.status, 'ok');
     assert.strictEqual(gateway.requests.at(-1).body.seats, 3);
-    const autoRenew = await qiweSubscriptionAutoRenew({ ...common, autoRenew: false });
+    const autoRenew = await qiweiSubscriptionAutoRenew({ ...common, autoRenew: false });
     assert.strictEqual(autoRenew.status, 'ok');
     assert.strictEqual(gateway.requests.at(-1).body.autoRenew, false);
     console.log('[ok] subscription tools use Fmode gateway routes');
 
-    const status = await qiweLoginStatus(common);
+    const status = await qiweiLoginStatus(common);
     assert.strictEqual(status.status, 'ok');
     assert.strictEqual(gateway.requests.at(-1).query.uid, common.uid);
-    const start = await qiweLoginStart({ ...common, openBrowser: false });
+    const start = await qiweiLoginStart({ ...common, openBrowser: false });
     assert.strictEqual(start.status, 'ok');
     assertProviderHidden(start);
-    const check = await qiweLoginCheck(common);
+    const check = await qiweiLoginCheck(common);
     assert.strictEqual(check.status, 'ok');
     assert.strictEqual(check.summary.loggedIn, true);
     assertProviderHidden(check);
-    const verify = await qiweLoginVerify({ ...common, code: '123456' });
+    const verify = await qiweiLoginVerify({ ...common, code: '123456' });
     assert.strictEqual(verify.status, 'ok');
     assert.deepStrictEqual(gateway.requests.at(-1).body, { uid: common.uid, code: '123456' });
     console.log('[ok] login tools use uid-based Fmode gateway routes and hide internal identifiers');

+ 5 - 5
claude-code/claude-code-qiwe-assistant/scripts/wecom-cli-smoke-test.js

@@ -5,10 +5,10 @@ const os = require('os');
 const path = require('path');
 
 async function main() {
-  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwe-official-cli-test-'));
+  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-official-cli-test-'));
   const runtimeRoot = path.join(tempRoot, 'runtime');
   const configDir = path.join(tempRoot, 'config');
-  process.env.QIWE_OFFICIAL_CLI_RUNTIME_DIR = runtimeRoot;
+  process.env.QIWEI_OFFICIAL_CLI_RUNTIME_DIR = runtimeRoot;
   process.env.WECOM_CLI_CONFIG_DIR = configDir;
 
   const runtime = require('../mcp/src/core/wecom-cli-runtime');
@@ -47,11 +47,11 @@ async function main() {
     assert.strictEqual(readyStatus.authorized, true);
     assert.strictEqual(readyStatus.ready, true);
 
-    const help = await provider.qiweOfficialHelp({ category: 'meeting', method: 'create_meeting' });
+    const help = await provider.qiweiOfficialHelp({ category: 'meeting', method: 'create_meeting' });
     assert.strictEqual(help.status, 'ok');
     assert.strictEqual(help.data.help, 'help:meeting.create_meeting');
 
-    const call = await provider.qiweOfficialCall({
+    const call = await provider.qiweiOfficialCall({
       category: 'doc',
       method: 'create_doc',
       args: { doc_type: 3, doc_name: '测试文档; echo unsafe' }
@@ -65,7 +65,7 @@ async function main() {
     assert(!Object.hasOwn(call.data.response, 'providerName'));
     assert.strictEqual(call.data.response.doc_url, 'https://docs.example.test/document/1');
 
-    const invalidMethod = await provider.qiweOfficialCall({
+    const invalidMethod = await provider.qiweiOfficialCall({
       category: 'doc',
       method: 'create_doc; echo unsafe',
       args: {}

+ 0 - 42
claude-code/claude-code-qiwe-assistant/skills/qiwe-login/SKILL.md

@@ -1,42 +0,0 @@
----
-name: qiwe-login
-description: 通过 Fmode 网关转发的企业微信接口完成扫码登录和订阅管理。适用于首次使用、设备掉线、qiwe_api_call 返回 needs_login、订阅未开通或席位不足;客户端只使用 Fmode 鉴权 token 和 uid,企业微信接口访问凭据由 Fmode 网关管理。
----
-
-# 企业微信扫码登录
-
-## 严格流程
-
-1. **检查订阅**:调用 `qiwe_subscription_status`。
-   - 未订阅或已到期:调用 `qiwe_subscribe`;
-   - 席位已满:用 `qiwe_subscribe` 传新的总 `seats` 数增购;
-   - 余额不足:引导用户充值飞马余额后重试。
-2. **检查设备**:调用 `qiwe_login_status`。
-   - `online=true`:无需重新登录;
-   - 未配置或离线:继续下一步。
-3. **生成二维码**:调用 `qiwe_login_start`。
-   - 工具通过 `/api/qiwe/login/start` 请求 Fmode 网关;
-   - 二维码保存到 `outputs/qiwe-login-qrcode.png`,默认打开 HTML 预览;
-   - 把二维码展示给用户,用手机企业微信扫码并确认。
-4. **轮询状态**:每 3–5 秒调用 `qiwe_login_check`。
-   - `-1 / 1`:继续等待;
-   - `0`:可传 `manual: true` 尝试免扫码登录;
-   - `2`:登录成功;
-   - `4`:用户取消,重新调用 `qiwe_login_start`;
-   - `10`:进入验证码流程。
-5. **验证码**:状态 `10` 时向用户索要手机端显示的 6 位验证码,调用 `qiwe_login_verify`,然后再次检查状态。
-6. **验证业务链路**:登录成功后调用 `qiwe_api_call`(例如 `user.getProfile`)。
-
-## 配置
-
-- `QIWE_API_BASE` 默认 `https://server.fmode.cn/api/qiwe`;
-- Fmode token 自动从 Claude Code / Fmode 配置读取,也可显式设置 `QIWE_AUTH_TOKEN`;
-- `QIWE_UID` 未配置时自动生成并持久化;
-- 不读取、保存或要求用户提供企业微信接口访问凭据。
-
-## 安全约定
-
-- 不展示或记录 Authorization 和 Fmode token;
-- 不向用户暴露接口服务提供方名称、相关域名或控制台;
-- 不绕过 Fmode 网关直连内部服务;
-- 不通过 `qiwe_api_call` 调用 `/login/*` 或 `/client/*`。

+ 8 - 8
claude-code/claude-code-qiwe-assistant/skills/qiwe-api-catalog/SKILL.md → claude-code/claude-code-qiwe-assistant/skills/qiwei-api-catalog/SKILL.md

@@ -1,6 +1,6 @@
 ---
-name: qiwe-api-catalog
-description: 通过 Fmode 网关转发的企业微信接口调用消息、群管理、联系人、朋友圈、标签、会话、账号和文件等 100+ 个能力;先查清单、读文档,再用 qiwe_api_call 调用。企业微信接口访问凭据由 Fmode 网关管理。
+name: qiwei-api-catalog
+description: 通过 Fmode 网关转发的企业微信接口调用消息、群管理、联系人、朋友圈、标签、会话、账号和文件等 100+ 个能力;先查清单、读文档,再用 qiwei_api_call 调用。企业微信接口访问凭据由 Fmode 网关管理。
 ---
 
 # 企业微信接口清单驱动调用
@@ -38,11 +38,11 @@ Fmode 网关负责:
 
 ## 标准流程
 
-1. 用 `qiwe_api_search` 按关键词、module 或 tag 找接口;
-2. 用 `qiwe_api_doc` 核对必填参数和返回字段;
-3. 用 `qiwe_api_call` 传 `id + params`;
-4. 收到订阅错误时调用 `qiwe_subscription_status`,必要时用 `qiwe_subscribe`;
-5. 收到登录态错误时调用 `qiwe_login_status`,必要时转 `qiwe-login` 技能重新扫码。
+1. 用 `qiwei_api_search` 按关键词、module 或 tag 找接口;
+2. 用 `qiwei_api_doc` 核对必填参数和返回字段;
+3. 用 `qiwei_api_call` 传 `id + params`;
+4. 收到订阅错误时调用 `qiwei_subscription_status`,必要时用 `qiwei_subscribe`;
+5. 收到登录态错误时调用 `qiwei_login_status`,必要时转 `qiwei-login` 技能重新扫码。
 
 清单里没有的普通 JSON 接口可以传 `rawMethod + params`。`/login/*`、`/client/*` 必须走登录专用工具,不能透传。
 
@@ -56,7 +56,7 @@ Fmode 网关负责:
 - `session`:会话分页与会话组;
 - `user`:个人与企业信息;
 - `cloud`:文件接口;multipart 文件直传目前不经过中转;
-- `login` / `client`:仅用于文档检索,实际操作走 `qiwe-login`。
+- `login` / `client`:仅用于文档检索,实际操作走 `qiwei-login`。
 
 ## 注意事项
 

+ 13 - 13
claude-code/claude-code-qiwe-assistant/skills/qiwe-capability-router/SKILL.md → claude-code/claude-code-qiwe-assistant/skills/qiwei-capability-router/SKILL.md

@@ -1,5 +1,5 @@
 ---
-name: qiwe-capability-router
+name: qiwei-capability-router
 description: 在 Fmode 网关转发的企业微信接口与官方 CLI 通道之间选择正确能力。文档、会议、日程、待办优先使用官方 CLI;客户、客户群、朋友圈、标签、个人企微设备登录等继续使用原有企业微信接口工具。
 ---
 
@@ -13,27 +13,27 @@ description: 在 Fmode 网关转发的企业微信接口与官方 CLI 通道之
 |---|---|
 | 普通文档、在线表格、智能表格、智能文档 | 企业微信官方 CLI |
 | 会议、日程、待办 | 企业微信官方 CLI |
-| 客户、外部联系人、客户群、朋友圈、标签 | Fmode 网关企业微信接口工具 `qiwe_api_*` |
-| 个人企微设备扫码登录、掉线恢复 | Fmode 网关登录工具 `qiwe_login_*` |
-| 订阅、席位、余额 | Fmode 网关订阅工具 `qiwe_subscription_*` |
+| 客户、外部联系人、客户群、朋友圈、标签 | Fmode 网关企业微信接口工具 `qiwei_api_*` |
+| 个人企微设备扫码登录、掉线恢复 | Fmode 网关登录工具 `qiwei_login_*` |
+| 订阅、席位、余额 | Fmode 网关订阅工具 `qiwei_subscription_*` |
 | 通讯录、消息 | 根据用户指定的身份和授权范围选择;不确定时先询问 |
 
 ## 官方 CLI 通道
 
-1. 调用 `qiwe_official_status`;
-2. CLI 未安装时调用 `qiwe_official_prepare`;
+1. 调用 `qiwei_official_status`;
+2. CLI 未安装时调用 `qiwei_official_prepare`;
 3. 未授权时把工具返回的初始化命令交给用户在终端运行;
-4. 用 `qiwe_official_help` 核对方法参数;
-5. 用 `qiwe_official_call` 执行。
+4. 用 `qiwei_official_help` 核对方法参数;
+5. 用 `qiwei_official_call` 执行。
 
 ## Fmode 网关接口通道
 
 原有流程保持不变:
 
-1. `qiwe_api_search`;
-2. `qiwe_api_doc`;
-3. `qiwe_api_call`;
-4. 登录问题转 `qiwe-login`;
-5. 订阅问题调用 `qiwe_subscription_*`。
+1. `qiwei_api_search`;
+2. `qiwei_api_doc`;
+3. `qiwei_api_call`;
+4. 登录问题转 `qiwei-login`;
+5. 订阅问题调用 `qiwei_subscription_*`。
 
 不得把官方机器人凭据传给 Fmode 网关,也不得用 Fmode token 初始化官方 CLI。

+ 42 - 0
claude-code/claude-code-qiwe-assistant/skills/qiwei-login/SKILL.md

@@ -0,0 +1,42 @@
+---
+name: qiwei-login
+description: 通过 Fmode 网关转发的企业微信接口完成扫码登录和订阅管理。适用于首次使用、设备掉线、qiwei_api_call 返回 needs_login、订阅未开通或席位不足;客户端只使用 Fmode 鉴权 token 和 uid,企业微信接口访问凭据由 Fmode 网关管理。
+---
+
+# 企业微信扫码登录
+
+## 严格流程
+
+1. **检查订阅**:调用 `qiwei_subscription_status`。
+   - 未订阅或已到期:调用 `qiwei_subscribe`;
+   - 席位已满:用 `qiwei_subscribe` 传新的总 `seats` 数增购;
+   - 余额不足:引导用户充值飞马余额后重试。
+2. **检查设备**:调用 `qiwei_login_status`。
+   - `online=true`:无需重新登录;
+   - 未配置或离线:继续下一步。
+3. **生成二维码**:调用 `qiwei_login_start`。
+   - 工具通过 `/api/qiwei/login/start` 请求 Fmode 网关;
+   - 二维码保存到 `outputs/qiwei-login-qrcode.png`,默认打开 HTML 预览;
+   - 把二维码展示给用户,用手机企业微信扫码并确认。
+4. **轮询状态**:每 3–5 秒调用 `qiwei_login_check`。
+   - `-1 / 1`:继续等待;
+   - `0`:可传 `manual: true` 尝试免扫码登录;
+   - `2`:登录成功;
+   - `4`:用户取消,重新调用 `qiwei_login_start`;
+   - `10`:进入验证码流程。
+5. **验证码**:状态 `10` 时向用户索要手机端显示的 6 位验证码,调用 `qiwei_login_verify`,然后再次检查状态。
+6. **验证业务链路**:登录成功后调用 `qiwei_api_call`(例如 `user.getProfile`)。
+
+## 配置
+
+- `QIWEI_API_BASE` 默认 `https://server.fmode.cn/api/qiwei`;
+- Fmode token 自动从 Claude Code / Fmode 配置读取,也可显式设置 `QIWEI_AUTH_TOKEN`;
+- `QIWEI_UID` 未配置时自动生成并持久化;
+- 不读取、保存或要求用户提供企业微信接口访问凭据。
+
+## 安全约定
+
+- 不展示或记录 Authorization 和 Fmode token;
+- 不向用户暴露接口服务提供方名称、相关域名或控制台;
+- 不绕过 Fmode 网关直连内部服务;
+- 不通过 `qiwei_api_call` 调用 `/login/*` 或 `/client/*`。

+ 7 - 7
claude-code/claude-code-qiwe-assistant/skills/qiwe-official-doc/SKILL.md → claude-code/claude-code-qiwe-assistant/skills/qiwei-official-doc/SKILL.md

@@ -1,11 +1,11 @@
 ---
-name: qiwe-official-doc
+name: qiwei-official-doc
 description: 通过企业微信官方 CLI 创建普通文档、读取 Markdown 内容和覆写文档。适用于企业微信文档或 doc.weixin.qq.com/doc/ 链接;在线表格、智能表格和智能文档应路由到后续对应技能。
 ---
 
 # 企业微信官方普通文档
 
-本技能只处理 URL 形如 `https://doc.weixin.qq.com/doc/*` 的普通文档,通过 `qiwe_official_*` MCP 工具调用官方 CLI。
+本技能只处理 URL 形如 `https://doc.weixin.qq.com/doc/*` 的普通文档,通过 `qiwei_official_*` MCP 工具调用官方 CLI。
 
 以下类型不由本技能直接处理:
 
@@ -17,10 +17,10 @@ description: 通过企业微信官方 CLI 创建普通文档、读取 Markdown 
 
 ## 前置检查
 
-1. 调用 `qiwe_official_status`;
-2. 未安装时调用 `qiwe_official_prepare`;
+1. 调用 `qiwei_official_status`;
+2. 未安装时调用 `qiwei_official_prepare`;
 3. 未初始化时停止业务调用,把返回的初始化命令交给用户完成扫码;
-4. 调用 `qiwe_official_help` 核对当前方法参数。
+4. 调用 `qiwei_official_help` 核对当前方法参数。
 
 ## 支持的方法
 
@@ -91,6 +91,6 @@ description: 通过企业微信官方 CLI 创建普通文档、读取 Markdown 
 ## 错误处理
 
 - `851002 incompatible doc type`:重新识别 URL 类型,不要继续用普通文档方法;
-- 未初始化:提示官方 CLI 初始化,不调用原有 `qiwe_login_*`;
-- 方法或 schema 变化:重新调用 `qiwe_official_help`;
+- 未初始化:提示官方 CLI 初始化,不调用原有 `qiwei_login_*`;
+- 方法或 schema 变化:重新调用 `qiwei_official_help`;
 - 不展示机器人 Secret、Authorization 或本地加密配置内容。

+ 7 - 7
claude-code/claude-code-qiwe-assistant/skills/qiwe-official-meeting/SKILL.md → claude-code/claude-code-qiwe-assistant/skills/qiwei-official-meeting/SKILL.md

@@ -1,16 +1,16 @@
 ---
-name: qiwe-official-meeting
+name: qiwei-official-meeting
 description: 通过企业微信官方 CLI 创建、查询、取消会议及维护受邀成员。适用于预约会议、查看会议列表或详情、取消会议、添加或移除参会人;不使用原有个人企微设备接口。
 ---
 
 # 企业微信官方会议
 
-所有会议操作通过本技能包的 `qiwe_official_*` MCP 工具调用官方 CLI,不直接拼接 shell 命令。
+所有会议操作通过本技能包的 `qiwei_official_*` MCP 工具调用官方 CLI,不直接拼接 shell 命令。
 
 ## 前置检查
 
-1. 调用 `qiwe_official_status`;
-2. 未安装时调用 `qiwe_official_prepare`,成功后再次检查状态;
+1. 调用 `qiwei_official_status`;
+2. 未安装时调用 `qiwei_official_prepare`,成功后再次检查状态;
 3. 未初始化时停止业务调用,把返回的初始化命令交给用户完成一次扫码;
 4. 初始化完成后再继续。
 
@@ -24,7 +24,7 @@ description: 通过企业微信官方 CLI 创建、查询、取消会议及维
 | 取消会议 | `meeting` | `cancel_meeting` |
 | 全量更新受邀成员 | `meeting` | `set_invite_meeting_members` |
 
-每次首次使用或参数不确定时,先调用 `qiwe_official_help` 读取当前固定 CLI 版本的方法 schema。
+每次首次使用或参数不确定时,先调用 `qiwei_official_help` 读取当前固定 CLI 版本的方法 schema。
 
 ## 创建会议
 
@@ -76,7 +76,7 @@ description: 通过企业微信官方 CLI 创建、查询、取消会议及维
 
 ## 错误处理
 
-- 未初始化:提示执行官方初始化命令,不转到原有 `qiwe_login_*`;
-- 方法或参数变化:重新调用 `qiwe_official_help`;
+- 未初始化:提示执行官方初始化命令,不转到原有 `qiwei_login_*`;
+- 方法或参数变化:重新调用 `qiwei_official_help`;
 - 官方通道失败不影响原有 Fmode 网关接口通道;
 - 不展示机器人 Secret、Authorization 或本地加密配置内容。