Skill: cat-xierluo/zhihe-legal-research (version 1.2.0)

--- name: zhihe-legal-research homepage: https://github.com/cat-xierluo/legal-skills author: 杨卫薪律师（微信ywxlaw） version: "1.2.0" license: MIT description: 连接智合AI法律大模型平台进行法律研究。本技能应在用户需要进行法律问题研究、查找法律法规、检索类似案例、或获取法律研究报告时使用。需要智合AI平台会员账号。 --- # 智合法律研究 连接智合AI法律大模型平台，提供专业的法律调研分析服务。 ## ⚠️ 异步任务处理机制 法律研究是**异步长任务**（3-10分钟）。采用**用户主动查询**机制，兼容所有 AI Agent 平台（Claude Code、OpenClaw 等）： ``` 用户提交 → 获得任务 ID → 用户稍后主动查询 → 获取结果并归档 ``` **核心原则：** - **不在每次消息前自动检查结果**——避免旧结果污染当前对话 - **仅在用户主动询问研究结果时**才查询状态和结果 - **每次查询完毕后立即归档**——确保状态干净，不留残留数据 --- ## 工作流程 ### 步骤 1：检查登录状态 ```bash ./scripts/auth.sh check ``` - `is_vip: true` → 已登录，继续 - `code: 401` → 需要登录（执行步骤 2） ### 步骤 2：登录（如需） ```bash # 发送验证码（如果已保存手机号，可省略手机号参数） ./scripts/auth.sh send-code [手机号] # 验证登录（自动保存 Token 和手机号） ./scripts/auth.sh verify <手机号> <6位验证码> ``` **自动重登流程**：如果 `config` 中已保存 `LEGAL_RESEARCH_PHONE`，token 失效时可直接执行 `./scripts/auth.sh send-code`（无需传入手机号），系统自动使用保存的手机号发送验证码。用户只需提供验证码即可完成重登。 ### 步骤 3：提交法律问题 ```bash # 提交问题 ./scripts/research.sh submit "<用户的法律问题>" ``` **提交成功后，记录 task_id 并告知用户：** > ✅ 您的法律问题已提交，后台正在进行调研分析。 > ⏱️ 预计需要 3-4 分钟完成。 > 📋 任务 ID：`{task_id}` > > 👉 请在约 4 分钟后回复"查看结果"或"研究结果好了吗"来获取分析报告。 ### 步骤 4：查询结果（仅当用户主动询问时） **触发条件：** 用户主动询问研究结果（如"查看结果"、"结果出来了吗"等）。 如果用户提供了 task_id，直接查询；如果未提供，先查历史获取最近任务： ```bash # 如果没有 task_id，先查历史 ./scripts/research.sh history 1 3 ``` 然后查询状态： ```bash # 查询状态 ./scripts/research.sh status <task_id> ``` **根据状态处理：** | 状态 | 处理方式 | |------|----------| | `completed` | 获取结果 → 获取报告 → 归档 → 展示给用户 | | `running` | 告知用户继续等待 1-2 分钟后再查询 | | `pending` | 告知用户仍在排队中，稍后再查 | | `failed` | 告知用户失败原因，建议重新提交 | | `timeout` | 告知用户超时，建议简化问题重试 | **当状态为 completed 时，依次执行：** ```bash # 1. 获取文字结果 ./scripts/research.sh result <task_id> # 2. 获取报告下载链接 ./scripts/research.sh report <task_id> # 3. 自动归档（下载报告到 archive/ 目录，含 Markdown 转换） ./scripts/research.sh archive <task_id> ``` 归档完成后，展示研究结果和报告链接给用户。 --- ## Claude Code 增强模式（可选） **仅在 Claude Code 环境下可用。** 提供更好的异步体验，无需用户手动查询。 提交任务后，使用 `Bash` 工具的 `run_in_background: true` 启动后台监控： ```bash command: "./scripts/monitor.sh monitor <task_id> 600 30" run_in_background: true timeout: 600000 ``` **关键注意事项：** 1. **timeout 必须设为 600000**（10分钟），否则默认 2 分钟会超时 2. 后台监控完成后会自动收到通知，此时展示结果给用户 3. 监控脚本会自动归档到 `archive/` 目录 **OpenClaw 环境不支持此增强模式**，请使用标准流程（步骤 1-4）。 --- ## 常用命令 ### 认证 | 命令 | 用途 | |------|------| | `./scripts/auth.sh check` | 检查登录状态 | | `./scripts/auth.sh send-code [phone]` | 发送验证码（省略手机号时使用已保存的号码） | | `./scripts/auth.sh verify <phone> <code>` | 验证登录（自动保存 Token 和手机号） | | `./scripts/auth.sh logout` | 清除凭证 | ### 研究操作 | 命令 | 用途 | |------|------| | `./scripts/research.sh submit "<query>"` | 提交问题 | | `./scripts/research.sh status <task_id>` | 查询状态 | | `./scripts/research.sh result <task_id>` | 获取结果 | | `./scripts/research.sh report <task_id>` | 获取报告链接 | | `./scripts/research.sh archive <task_id>` | 归档研究结果 | | `./scripts/research.sh history [page] [size]` | 查看历史任务 | ### 监控管理（仅 Claude Code 增强模式） | 命令 | 用途 | |------|------| | `./scripts/monitor.sh monitor <task_id> [timeout] [interval]` | 阻塞监控 | | `./scripts/monitor.sh status` | 查看监控状态 | | `./scripts/monitor.sh results` | 获取已完成待通知的结果 | | `./scripts/monitor.sh clear <task_id>` | 标记为已通知 | --- ## 状态处理 | 状态 | 说明 | 处理 | |------|------|------| | `pending` | 排队中 | 告知用户等待 | | `running` | 处理中 | 告知用户继续等待 | | `completed` | 已完成 | 获取结果 → 归档 → 通知用户 | | `failed` | 失败 | 通知用户失败原因 | | `timeout` | 超时 | 通知用户超时 | --- ## 配置 所有配置文件自包含在 skill 内部：`assets/` | 文件 | 用途 | |------|------| | `.env` | Token 和手机号配置（已加入 .gitignore） | | `pending.json` | 待处理任务 | | `completed.json` | 已完成待通知 | | `notified.json` | 已通知历史 | **注意**：`assets/.env` 已加入 `.gitignore`，敏感信息不会被提交到 git。 配置示例见 [assets/.env.example](assets/.env.example) --- ## 归档功能 ### 自动归档 **任务完成时自动归档**：归档时会自动下载报告并保存到 `archive/` 目录。 **归档命名格式**：`YYMMDD 主题_法律研究报告` 示例： ``` archive/ ├── 260326 美术作品著作权侵权纠纷_法律研究报告/ │ ├── result.md # 研究结果摘要（Markdown） │ ├── report.docx # 详细报告（自动下载） │ ├── report.md # 报告 Markdown 版本（需安装 pandoc） │ └── media/ # 报告中的图片（如有） └── 260310 劳动合同解除赔偿_法律研究报告/ └── result.md ``` ### 手动归档 ```bash # 归档研究结果（自动下载报告并转换为 Markdown） ./scripts/research.sh archive <task_id> # 列出所有归档 ./scripts/research.sh list-archive ``` ### Markdown 转换依赖 归档时会自动尝试将 docx 报告转换为 Markdown： - **依赖**：需要安装 [pandoc](https://pandoc.org/) - **格式**：使用 GitHub Flavored Markdown (GFM)，保留原始层级结构 - **图片**：自动提取到 `media/` 子目录 ```bash # macOS 安装 pandoc brew install pandoc ``` --- ## 详细文档 - [API 参考文档](references/api-reference.md) - 完整 API 说明 - [交互示例](references/interaction-examples.md) - 各种场景的完整流程 --- ## 注意事项 1. **会员要求**：需要智合AI平台会员，非会员引导至 https://www.zhiexa.com 2. **Token 有效期**：72 小时，过期需重新登录 3. **频率限制**：每分钟仅可提交 1 个问题，同时只能有 1 个进行中任务 4. **报告链接**：有效期 7 天，过期后重新调用接口会自动刷新 5. **隐私保护**：手机号仅用于鉴权，展示时脱敏 6. **跨平台兼容**：核心流程仅依赖 bash 脚本，兼容所有 Agent 平台（Claude Code、OpenClaw）

path: CHANGELOG.md
content: # 变更日志

## [1.2.1] - 2026-04-08

### 新增

- 手机号持久化存储：登录后自动保存手机号，token 过期时可省略手机号直接发送验证码
- 自动重登流程：检测到 token 失效后只需提供验证码即可完成重登

### 修复

- 处理服务端 500 错误：token 过期时服务端返回 HTML 而非 JSON，导致 auth check 崩溃
- 修复报告下载：报告 API 不返回 `has_report` 字段，改用 `code` 状态码判断
- 修复 Unicode 文件名：中文全角问号 `？` 导致 `mkdir` 失败，改用 python3 处理
- 修复归档文件名：`result.md` 改为主题命名，Word 转 MD 使用 `_报告.md` 后缀避免覆盖
- 修复 query 提取：monitor.sh 从 status API 提取 query（result API 不含该字段）
- 修复 JSON 注入：`submit_query`、`send_code`、`verify_code` 改用 `jq` 构造 JSON

### 改进

- 配置文件从 `config` 重命名为 `.env`，更符合惯例
- 归档帮助文本更新为实际目录结构

## [1.2.0] - 2026-04-06

### 改进

- 重写异步任务处理机制：去掉 Sub Agent 后台监控，改为用户主动查询，兼容 Claude Code 和 OpenClaw
- 去掉"每次消息前自动检查结果"机制，避免旧结果污染当前对话
- 新增 Claude Code 增强模式：使用 `Bash run_in_background` 可选后台监控
- 清理 completed.json/notified.json 中的残留空 query 旧数据
- Front Matter 规范化：补充 homepage、author、version 字段，license 格式统一
- 修复 name 字段大小写（Zhihe-Legal-Research → zhihe-legal-research）
- 更新交互示例文档，移除 Sub Agent 相关示例

## [1.1.0] - 2026-03-10

### 新增

- Sub Agent 后台监控机制：提交问题后自动启动 Sub Agent 监控
- 状态持久化：任务状态保存到 assets/ 目录
- 主动通知：每次用户发消息时自动检查已完成任务
- 新增 monitor.sh 脚本：管理后台监控和结果通知
- 支持用户在等待期间继续其他任务

### 改进

- 用户无需手动发"查看结果"，系统自动检测并通知
- 优化用户体验：提交后可做别的事，完成后主动告知

## [1.0.0] - 2026-03-10

### 新增

- 初始版本发布
- 支持登录认证（手机号 + 验证码）
- 支持提交法律研究问题
- 支持查询任务状态和获取结果
- 支持获取 docx 格式研究报告
- 支持阻塞式轮询等待（wait 命令）
- 支持查看历史记录
- Token 管理脚本（存储到 assets/config）
- 将所有 curl 命令封装为独立脚本
- 将 API 文档移至 references/
- 添加配置模板 config.example


path: LICENSE.txt
content: MIT License

Copyright (c) 2026 杨卫薪律师（微信ywxlaw）

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


path: references/api-reference.md
content: # 智合法律研究 API 参考文档

## 基础信息

| 项目 | 值 |
|------|-----|
| 基础 URL | `https://fc-openresearch-qzquocekez.cn-shanghai.fcapp.run` |
| 认证方式 | Bearer Token (JWT) |
| Token 有效期 | 72 小时 |
| Content-Type | `application/json` |

---

## 认证接口

### 1. 发送验证码

**请求**
```http
POST /api/auth/send-code
Content-Type: application/json

{
  "phone": "13800138000"
}
```

**响应**
```json
{
  "code": 200,
  "message": "验证码已发送",
  "data": {
    "masked_phone": "138****8000"
  }
}
```

**错误码**
| 状态码 | 说明 |
|--------|------|
| 400 | 手机号格式错误 |
| 429 | 验证码发送过于频繁 |

---

### 2. 验证登录

**请求**
```http
POST /api/auth/verify-code
Content-Type: application/json

{
  "phone": "13800138000",
  "code": "123456"
}
```

**响应**
```json
{
  "code": 200,
  "message": "登录成功",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "is_vip": true,
    "vip_type": "豪华版",
    "expire_at": "2026-03-13T14:30:00Z"
  }
}
```

**错误码**
| 状态码 | 说明 |
|--------|------|
| 400 | 验证码错误或已过期 |
| 404 | 用户不存在 |

---

## 用户接口

### 3. 获取用户信息

**请求**
```http
GET /api/user/profile
Authorization: Bearer <token>
```

**响应**
```json
{
  "code": 200,
  "data": {
    "phone": "138****8000",
    "is_vip": true,
    "vip_type": "豪华版",
    "vip_expire_at": "2026-12-31T23:59:59Z"
  }
}
```

**错误码**
| 状态码 | 说明 |
|--------|------|
| 401 | Token 无效或已过期 |

---

## 研究接口

### 4. 提交研究问题

**请求**
```http
POST /api/research/query
Authorization: Bearer <token>
Content-Type: application/json

{
  "query": "劳动合同到期不续签需要赔偿吗？"
}
```

**响应**
```json
{
  "code": 200,
  "message": "已提交，正在进行法律调研分析",
  "data": {
    "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "estimated_completion": "14:35:30",
    "estimated_seconds": 210
  }
}
```

**错误码**
| 状态码 | 说明 |
|--------|------|
| 401 | 未认证 |
| 403 | 非会员 |
| 429 | 频率限制（每分钟1次，或已有任务进行中） |

---

### 5. 查询任务状态

**请求**
```http
GET /api/research/status/<task_id>
Authorization: Bearer <token>
```

**响应**
```json
{
  "code": 200,
  "data": {
    "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "completed",
    "progress": 100,
    "has_report": true,
    "created_at": "2026-03-10T14:30:00Z",
    "completed_at": "2026-03-10T14:34:30Z"
  }
}
```

**状态值**
| 状态 | 说明 |
|------|------|
| `pending` | 排队中 |
| `running` | 处理中 |
| `completed` | 已完成 |
| `failed` | 失败 |
| `timeout` | 超时 |

---

### 6. 获取研究结果

**请求**
```http
GET /api/research/result/<task_id>
Authorization: Bearer <token>
```

**响应**
```json
{
  "code": 200,
  "data": {
    "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "query": "劳动合同到期不续签需要赔偿吗？",
    "text_result": "根据《劳动合同法》第四十六条...",
    "created_at": "2026-03-10T14:34:30Z"
  }
}
```

---

### 7. 获取报告下载链接

**请求**
```http
GET /api/research/report/<task_id>
Authorization: Bearer <token>
```

**响应**
```json
{
  "code": 200,
  "data": {
    "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "has_report": true,
    "report_url": "https://storage.example.com/reports/xxx.docx?sign=...",
    "filename": "法律研究报告_20260310_143430.docx",
    "expires_at": "2026-03-17T14:34:30Z"
  }
}
```

**注意**：报告链接有效期 7 天，过期后重新调用接口会自动刷新签名。

---

### 8. 查看历史任务

**请求**
```http
GET /api/research/history?page=1&size=10
Authorization: Bearer <token>
```

**响应**
```json
{
  "code": 200,
  "data": {
    "total": 25,
    "page": 1,
    "size": 10,
    "tasks": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "query": "劳动合同到期不续签需要赔偿吗？",
        "status": "completed",
        "has_report": true,
        "created_at": "2026-03-10T14:30:00Z"
      }
    ]
  }
}
```

---

## 错误处理

### 通用错误响应格式

```json
{
  "code": 400,
  "message": "错误描述",
  "data": null
}
```

### HTTP 状态码对照

| HTTP 状态码 | 含义 | 处理建议 |
|-------------|------|----------|
| 200 | 成功 | - |
| 400 | 参数错误 | 检查请求参数格式 |
| 401 | 未认证/Token过期 | 重新登录 |
| 403 | 非会员/无权限 | 引导开通会员 |
| 404 | 资源不存在 | 检查 task_id 或通过 history 查询 |
| 429 | 频率限制 | 稍后重试 |
| 502 | 上游服务错误 | 稍后重试 |

---

## 限流规则

| 接口 | 限制 |
|------|------|
| 发送验证码 | 每手机号 1次/分钟 |
| 提交研究问题 | 每用户 1次/分钟，同时只能有1个进行中任务 |
| 其他接口 | 无特殊限制 |

---

## 调试技巧

### 使用 curl 测试

```bash
# 设置 Token
export [REDACTED_SECRET]"

# 检查登录状态
curl -s -X GET "https://fc-openresearch-qzquocekez.cn-shanghai.fcapp.run/api/user/profile" \
  -H "Authorization: Bearer $TOKEN" | jq

# 提交问题
curl -s -X POST "https://fc-openresearch-qzquocekez.cn-shanghai.fcapp.run/api/research/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "测试问题"}' | jq
```

### 使用脚本工具

```bash
# 使用封装好的脚本
./scripts/auth.sh check
./scripts/research.sh submit "测试问题"
./scripts/research.sh status <task_id>
```


path: references/interaction-examples.md
content: # 智合法律研究 - 交互示例

本文档展示各种使用场景下的完整交互流程。

---

## 场景一：标准流程（用户主动查询）

用户提问 → 提交问题 → 用户稍后查询 → 查看结果

```
用户：劳动合同到期不续签需要赔偿吗？

你：[执行 ./scripts/auth.sh check → 登录有效]
    [执行 ./scripts/research.sh submit "劳动合同到期不续签需要赔偿吗？"]
    [获得 task_id: a1b2c3d4]

    回复：
    ✅ 您的法律问题已提交，后台正在进行调研分析。
    ⏱️ 预计需要 3-4 分钟完成。
    📋 任务 ID：`a1b2c3d4`

    👉 请在约 4 分钟后回复"查看结果"来获取分析报告。

--- 用户等待约 4 分钟 ---

用户：查看结果

你：[执行 ./scripts/research.sh status a1b2c3d4 → completed]
    [执行 ./scripts/research.sh result a1b2c3d4 → 获取文字结果]
    [执行 ./scripts/research.sh report a1b2c3d4 → 获取报告链接]
    [执行 ./scripts/research.sh archive a1b2c3d4 → 自动归档]

    回复：
    📋 **法律分析结果：**

    根据《劳动合同法》第四十六条的规定，有下列情形之一的，用人单位应当向劳动者支付经济补偿...

    （完整分析文本）

    📄 **详细报告：**
    [点击下载完整法律研究报告](https://...)
    文件名：法律研究报告_20260310_143530.docx

    📁 已自动归档到 archive/ 目录。
```

---

## 场景二：Claude Code 后台监控模式

适用于 Claude Code 环境，自动监控无需用户手动查询。

```
用户：帮我查一下商标侵权的赔偿标准，好了直接告诉我

你：[提交问题 → 获得 task_id: xyz789]
    [使用 Bash 工具启动后台监控]

    Bash 工具参数：
    - command: "/path/to/scripts/monitor.sh monitor xyz789 600 30"
    - run_in_background: true
    - timeout: 600000

    回复：
    ✅ 您的法律问题已提交，后台正在进行调研分析。
    ⏱️ 预计需要 3-4 分钟完成。
    🔄 后台自动监控中，完成后会主动通知您。

    [等待后台监控完成...]

    收到后台任务完成通知后：

    回复：
    ✅ 您的法律研究已完成！

    📋 **法律分析结果：**
    ...

    📄 **详细报告：**
    [下载链接]
```

---

## 场景三：首次使用需要登录

```
用户：帮我研究一下竞业限制的法律问题

你：[执行 ./scripts/auth.sh check → Token 不存在]

    回复：
    我来帮您进行法律研究。首次使用需要验证身份。

    请提供您的手机号码（需要智合AI平台会员账号）。

用户：13800138000

你：[执行 ./scripts/auth.sh send-code 13800138000]
    [验证码已发送]

    回复：
    验证码已发送至 138****8000，请输入收到的6位验证码。

用户：123456

你：[执行 ./scripts/auth.sh verify 13800138000 123456]
    [登录成功，Token 已保存]

    回复：
    ✅ 登录成功！您是豪华版会员。

    现在为您提交法律研究问题...

    [执行 ./scripts/research.sh submit "竞业限制的法律问题"]
    [获得 task_id]

    ✅ 您的法律问题已提交。
    👉 请在约 4 分钟后回复"查看结果"。
```

---

## 场景四：Token 过期需要重新登录

```
用户：查看结果

你：[执行 ./scripts/research.sh status <task_id>]
    [返回 401 错误]

    回复：
    您的登录凭证已过期（有效期72小时），需要重新登录。

    请提供您的手机号码。

用户：13800138000

你：[重新执行登录流程...]
```

---

## 场景五：结果还没完成

```
用户：查看结果

你：[执行 ./scripts/research.sh status <task_id>]
    [状态：running]

    回复：
    ⏳ 您的法律研究还在处理中，请再等 1-2 分钟后再发送"查看结果"。
```

---

## 场景六：非会员用户

```
用户：帮我研究一个法律问题

你：[执行 ./scripts/auth.sh check]
    [is_vip: false]

    回复：
    抱歉，法律研究服务需要智合AI平台会员资格。

    您可以访问 https://www.zhiexa.com 开通会员后继续使用。
```

---

## 场景七：用户不记得 task_id

```
用户：结果出来了吗

你：[不确定 task_id，执行 ./scripts/research.sh history 1 3]
    [获取最近任务列表]
    [找到最近未完成的任务: xyz789]
    [执行 ./scripts/research.sh status xyz789]
    [状态：completed → 获取结果并归档]

    回复：
    找到您最近的研究任务，结果如下：

    📋 **法律分析结果：**
    ...
```

---

## 场景八：查看历史记录

```
用户：我之前研究过什么法律问题？

你：[执行 ./scripts/research.sh history 1 10]

    回复：
    📜 **您的历史研究记录：**

    1. 劳动合同到期不续签需要赔偿吗？
       状态：已完成
       时间：2026-03-10 14:30

    2. 商标侵权的赔偿标准
       状态：已完成
       时间：2026-03-09 10:15

    ...

    如需查看某个研究的详细结果，请告诉我对应的序号。
```

---

## 场景九：研究失败或超时

```
用户：查看结果

你：[执行 ./scripts/research.sh status <task_id>]
    [状态：failed 或 timeout]

    回复：
    ❌ 您的法律研究遇到了问题（失败/超时）。

    建议您：
    1. 简化问题后重新提交
    2. 将复杂问题拆分为多个小问题
    3. 稍后重试

    是否需要重新提交？
```

---

## 脚本命令速查

| 场景 | 命令 |
|------|------|
| 检查登录状态 | `./scripts/auth.sh check` |
| 发送验证码 | `./scripts/auth.sh send-code <phone>` |
| 验证登录 | `./scripts/auth.sh verify <phone> <code>` |
| 提交问题 | `./scripts/research.sh submit "<query>"` |
| 查询状态 | `./scripts/research.sh status <task_id>` |
| 获取结果 | `./scripts/research.sh result <task_id>` |
| 获取报告 | `./scripts/research.sh report <task_id>` |
| 归档结果 | `./scripts/research.sh archive <task_id>` |
| 查看历史 | `./scripts/research.sh history [page] [size]` |
| 登出 | `./scripts/auth.sh logout` |


path: scripts/auth.sh
content: #!/bin/bash
# 智合法律研究 - 认证脚本
# 用法:
#   ./auth.sh check              - 检查登录状态
#   ./auth.sh send-code <phone>  - 发送验证码
#   ./auth.sh verify <phone> <code> - 验证登录
#   ./auth.sh logout             - 清除登录凭证
#
# 配置文件:
#   assets/.env - 存储 Token 和手机号（自包含在 skill 内部）
#
# 环境变量:
#   LEGAL_RESEARCH_TOKEN - JWT Token (自动加载)

set -e

# 获取 skill 根目录（脚本所在目录的上级）
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

# 基础配置
BASE_URL="https://fc-openresearch-qzquocekez.cn-shanghai.fcapp.run"
CONFIG_DIR="${SKILL_ROOT}/assets"
ENV_FILE="${CONFIG_DIR}/.env"

# 确保配置目录存在
ensure_config_dir() {
    mkdir -p "$CONFIG_DIR"
    chmod 700 "$CONFIG_DIR"
}

# 加载环境变量
load_token() {
    if [[ -f "$ENV_FILE" ]]; then
        source "$ENV_FILE" 2>/dev/null || true
    fi
    export LEGAL_RESEARCH_TOKEN
    export LEGAL_RESEARCH_PHONE
}

# 检查登录状态
check_auth() {
    load_token

    if [[ -z "$LEGAL_RESEARCH_TOKEN" ]]; then
        echo '{"code": 401, "message": "未登录，请先执行登录流程", "is_vip": false}'
        return 1
    fi

    local response
    response=$(curl -s -X GET "${BASE_URL}/api/user/profile" \
        -H "Authorization: Bearer ${LEGAL_RESEARCH_TOKEN}" \
        -H "Content-Type: application/json")

    # 响应不是有效 JSON（如服务端 500 HTML 错误页），视为 token 失效
    if [[ ! "$response" =~ ^\{ ]]; then
        echo '{"code": 401, "message": "登录状态已失效，请重新登录", "is_vip": false}'
        return 1
    fi

    echo "$response"
}

# 发送验证码
send_code() {
    local phone="$1"

    # 如果未提供手机号，尝试使用保存的手机号
    if [[ -z "$phone" ]]; then
        load_token
        phone="$LEGAL_RESEARCH_PHONE"
    fi

    if [[ -z "$phone" ]]; then
        echo '{"code": 400, "message": "请提供手机号（未找到已保存的手机号）"}'
        return 1
    fi

    # 验证手机号格式
    if [[ ! "$phone" =~ ^1[0-9]{10}$ ]]; then
        echo '{"code": 400, "message": "手机号格式错误，需要11位中国手机号（1开头）"}'
        return 1
    fi

    local payload
    payload=$(jq -n --arg p "$phone" '{"phone": $p}')

    curl -s -X POST "${BASE_URL}/api/auth/send-code" \
        -H "Content-Type: application/json" \
        -d "$payload"
}

# 验证登录
verify_code() {
    local phone="$1"
    local code="$2"

    if [[ -z "$phone" || -z "$code" ]]; then
        echo '{"code": 400, "message": "请提供手机号和验证码"}'
        return 1
    fi

    # 验证码格式
    if [[ ! "$code" =~ ^[0-9]{6}$ ]]; then
        echo '{"code": 400, "message": "验证码格式错误，需要6位数字"}'
        return 1
    fi

    local payload
    payload=$(jq -n --arg p "$phone" --arg c "$code" '{"phone": $p, "code": $c}')

    local response
    response=$(curl -s -X POST "${BASE_URL}/api/auth/verify-code" \
        -H "Content-Type: application/json" \
        -d "$payload")

    # 提取 token 并保存
    local token
    [REDACTED_SECRET] "$response" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)

    if [[ -n "$token" ]]; then
        ensure_config_dir

        # 更新或添加 TOKEN
        if [[ -f "$ENV_FILE" ]] && grep -q "^LEGAL_RESEARCH_TOKEN=" "$ENV_FILE" 2>/dev/null; then
            if [[ "$(uname)" == "Darwin" ]]; then
                sed -i '' "s|^LEGAL_RESEARCH_TOKEN=.*|LEGAL_RESEARCH_TOKEN=${token}|" "$ENV_FILE"
            else
                sed -i "s|^LEGAL_RESEARCH_TOKEN=.*|LEGAL_RESEARCH_TOKEN=${token}|" "$ENV_FILE"
            fi
        else
            echo "LEGAL_RESEARCH_TOKEN=${token}" >> "$ENV_FILE"
        fi

        # 更新或添加 PHONE
        if [[ -f "$ENV_FILE" ]] && grep -q "^LEGAL_RESEARCH_PHONE=" "$ENV_FILE" 2>/dev/null; then
            if [[ "$(uname)" == "Darwin" ]]; then
                sed -i '' "s|^LEGAL_RESEARCH_PHONE=.*|LEGAL_RESEARCH_PHONE=${phone}|" "$ENV_FILE"
            else
                sed -i "s|^LEGAL_RESEARCH_PHONE=.*|LEGAL_RESEARCH_PHONE=${phone}|" "$ENV_FILE"
            fi
        else
            echo "LEGAL_RESEARCH_PHONE=${phone}" >> "$ENV_FILE"
        fi

        # 设置文件权限
        chmod 600 "$ENV_FILE"

        export LEGAL_RESEARCH_TOKEN="$token"
        echo "$response"
    else
        echo "$response"
        return 1
    fi
}

# 登出（清除凭证）
logout() {
    if [[ -f "$ENV_FILE" ]]; then
        rm -f "$ENV_FILE"
        echo '{"code": 200, "message": "已清除登录凭证"}'
    else
        echo '{"code": 200, "message": "无存储的凭证"}'
    fi
    unset LEGAL_RESEARCH_TOKEN
}

# 显示配置信息
show_config() {
    echo "配置目录: ${CONFIG_DIR}"
    echo "配置文件: ${ENV_FILE}"
    if [[ -f "$ENV_FILE" ]]; then
        echo "文件状态: 存在"
        if [[ -n "$LEGAL_RESEARCH_TOKEN" ]]; then
            echo "Token: 已设置 (${LEGAL_RESEARCH_TOKEN:0:20}...)"
        else
            echo "Token: 未设置"
        fi
    else
        echo "文件状态: 不存在"
    fi
}

# 主入口
case "${1:-}" in
    check)
        check_auth
        ;;
    send-code)
        send_code "$2"
        ;;
    verify)
        verify_code "$2" "$3"
        ;;
    logout)
        logout
        ;;
    config)
        load_token
        show_config
        ;;
    *)
        echo "用法: $0 <command> [args]"
        echo ""
        echo "命令:"
        echo "  check                  检查登录状态"
        echo "  send-code <phone>      发送验证码到指定手机"
        echo "  verify <phone> <code>  验证登录并保存 Token"
        echo "  logout                 清除登录凭证"
        echo "  config                 显示配置信息"
        echo ""
        echo "配置存储位置: assets/.env（skill 内部）"
        exit 1
        ;;
esac


path: scripts/research.sh
content: #!/bin/bash
# 智合法律研究 - 研究操作脚本
# 用法:
#   ./research.sh submit "<query>"           - 提交法律研究问题
#   ./research.sh status <task_id>           - 查询任务状态
#   ./research.sh result <task_id>           - 获取文字分析结果
#   ./research.sh report <task_id>           - 获取报告下载链接
#   ./research.sh history [page] [size]      - 查看历史任务
#   ./research.sh wait <task_id> [timeout]   - 轮询等待任务完成（阻塞式）
#   ./research.sh archive <task_id>          - 归档研究结果到 archive/
#
# 环境变量:
#   LEGAL_RESEARCH_TOKEN - JWT Token

set -e
export LC_ALL=UTF-8

# 获取 skill 根目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

# 基础配置
BASE_URL="https://fc-openresearch-qzquocekez.cn-shanghai.fcapp.run"
CONFIG_DIR="${SKILL_ROOT}/assets"
ENV_FILE="${CONFIG_DIR}/.env"
ARCHIVE_DIR="${SKILL_ROOT}/archive"

# 默认轮询配置
DEFAULT_POLL_INTERVAL=15    # 轮询间隔（秒）
DEFAULT_TIMEOUT=600         # 默认超时（10分钟）

# 加载环境变量
load_token() {
    # 优先从 skill 专属配置加载
    if [[ -f "$ENV_FILE" ]]; then
        source "$ENV_FILE" 2>/dev/null || true
    fi
    # 兼容旧位置
    if [[ -z "$LEGAL_RESEARCH_TOKEN" && -f "${HOME}/.openclaw/.env" ]]; then
        source "${HOME}/.openclaw/.env" 2>/dev/null || true
    fi
    export LEGAL_RESEARCH_TOKEN
}

# 检查 Token
check_token() {
    if [[ -z "$LEGAL_RESEARCH_TOKEN" ]]; then
        echo '{"code": 401, "message": "未登录，请先执行 ./auth.sh 登录"}'
        return 1
    fi
}

# 提交研究问题
submit_query() {
    local query="$1"

    if [[ -z "$query" ]]; then
        echo '{"code": 400, "message": "请提供法律问题"}'
        return 1
    fi

    load_token
    check_token

    local payload
    payload=$(jq -n --arg q "$query" '{"query": $q}')

    curl -s -X POST "${BASE_URL}/api/research/query" \
        -H "Authorization: Bearer ${LEGAL_RESEARCH_TOKEN}" \
        -H "Content-Type: application/json" \
        -d "$payload"
}

# 查询任务状态
get_status() {
    local task_id="$1"

    if [[ -z "$task_id" ]]; then
        echo '{"code": 400, "message": "请提供 task_id"}'
        return 1
    fi

    load_token
    check_token

    curl -s -X GET "${BASE_URL}/api/research/status/${task_id}" \
        -H "Authorization: Bearer ${LEGAL_RESEARCH_TOKEN}"
}

# 获取研究结果
get_result() {
    local task_id="$1"

    if [[ -z "$task_id" ]]; then
        echo '{"code": 400, "message": "请提供 task_id"}'
        return 1
    fi

    load_token
    check_token

    curl -s -X GET "${BASE_URL}/api/research/result/${task_id}" \
        -H "Authorization: Bearer ${LEGAL_RESEARCH_TOKEN}"
}

# 获取报告下载链接
get_report() {
    local task_id="$1"

    if [[ -z "$task_id" ]]; then
        echo '{"code": 400, "message": "请提供 task_id"}'
        return 1
    fi

    load_token
    check_token

    curl -s -X GET "${BASE_URL}/api/research/report/${task_id}" \
        -H "Authorization: Bearer ${LEGAL_RESEARCH_TOKEN}"
}

# 查看历史任务
get_history() {
    local page="${1:-1}"
    local size="${2:-10}"

    load_token
    check_token

    curl -s -X GET "${BASE_URL}/api/research/history?page=${page}&size=${size}" \
        -H "Authorization: Bearer ${LEGAL_RESEARCH_TOKEN}"
}

# 轮询等待任务完成
wait_for_completion() {
    local task_id="$1"
    local timeout="${2:-$DEFAULT_TIMEOUT}"
    local interval="${3:-$DEFAULT_POLL_INTERVAL}"

    if [[ -z "$task_id" ]]; then
        echo '{"code": 400, "message": "请提供 task_id"}'
        return 1
    fi

    load_token
    check_token

    local elapsed=0
    local status=""

    echo "开始轮询任务状态 (task_id: ${task_id})"
    echo "超时: ${timeout}秒, 轮询间隔: ${interval}秒"
    echo "---"

    while [[ $elapsed -lt $timeout ]]; do
        status=$(get_status "$task_id")
        local state
        state=$(echo "$status" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)

        echo "[$(date '+%H:%M:%S')] 状态: ${state:-unknown}"

        case "$state" in
            completed)
                echo "---"
                echo "任务完成！"
                echo "$status"
                return 0
                ;;
            failed)
                echo "---"
                echo "任务失败！"
                echo "$status"
                return 1
                ;;
            timeout)
                echo "---"
                echo "任务超时！"
                echo "$status"
                return 1
                ;;
            running|pending)
                sleep "$interval"
                elapsed=$((elapsed + interval))
                ;;
            *)
                echo "未知状态: $state"
                echo "$status"
                return 1
                ;;
        esac
    done

    echo "---"
    echo "轮询超时（${timeout}秒）"
    return 1
}

# 一键获取完整结果（状态+结果+报告）
get_full_result() {
    local task_id="$1"

    if [[ -z "$task_id" ]]; then
        echo '{"code": 400, "message": "请提供 task_id"}'
        return 1
    fi

    load_token
    check_token

    # 先检查状态
    local status_response
    status_response=$(get_status "$task_id")
    local state
    state=$(echo "$status_response" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)

    if [[ "$state" != "completed" ]]; then
        echo "$status_response"
        return 1
    fi

    # 获取结果
    local result_response
    result_response=$(get_result "$task_id")

    # 获取报告（如果有）
    local report_response
    report_response=$(get_report "$task_id" 2>/dev/null || echo '{"code":404}')

    # 组合输出
    echo "{"
    echo "\"status\": \"completed\","
    echo "\"task_id\": \"${task_id}\","
    echo "\"result\": ${result_response},"
    echo "\"report\": ${report_response}"
    echo "}"
}

# 生成归档目录名（格式：YYMMDD 主题_法律研究报告）
generate_archive_name() {
    local query="$1"
    local date_prefix
    date_prefix=$(date '+%y%m%d')

    # 提取研究主题：尝试从问题中提取关键词
    local topic
    # 移除特殊字符，提取前50个字符作为主题
    topic=$(python3 -c "
import re, sys
q = sys.argv[1]
q = re.sub(r'[/\\\\:*?\"<>|？]', ' ', q)
q = q.lstrip('：:')
q = q[:50].rstrip()
print(q)
" "$query")

    # 组合命名：YYMMDD 主题_法律研究报告
    echo "${date_prefix} ${topic}_法律研究报告"
}

# 生成报告文件名（格式：YYMMDD 主题_法律研究报告.md）
generate_report_filename() {
    local query="$1"
    local date_prefix
    date_prefix=$(date '+%y%m%d')

    # 提取主题关键词（与文件夹名保持一致）
    local topic
    topic=$(python3 -c "
import re, sys
q = sys.argv[1]
q = re.sub(r'[/\\\\:*?\"<>|？]', ' ', q)
q = q.lstrip('：:')
q = q[:50].rstrip()
print(q)
" "$query")

    echo "${date_prefix} ${topic}_法律研究报告.md"
}

# 将 docx 转换为 Markdown（需要 pandoc）
convert_docx_to_md() {
    local docx_file="$1"
    local md_file="$2"

    if command -v pandoc &>/dev/null; then
        # 使用 gfm (GitHub Flavored Markdown) 格式，保留层级结构，提取图片
        pandoc -f docx -t gfm --wrap=none --extract-media="${docx_file%/*}/media" "$docx_file" -o "$md_file" 2>/dev/null
        return $?
    else
        return 1
    fi
}

# 归档研究结果
archive_result() {
    local task_id="$1"

    if [[ -z "$task_id" ]]; then
        echo '{"code": 400, "message": "请提供 task_id"}'
        return 1
    fi

    load_token
    check_token

    # 获取任务状态和查询内容
    local status_response
    status_response=$(get_status "$task_id")
    local state
    state=$(echo "$status_response" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)

    if [[ "$state" != "completed" ]]; then
        echo "{\"code\": 400, \"message\": \"任务未完成，当前状态: ${state}\"}"
        return 1
    fi

    # 获取结果
    local result_response
    result_response=$(get_result "$task_id")

    # 从 status 获取 query（result API 不含 query 字段）
    local query
    query=$(echo "$status_response" | jq -r '.data.query // ""' 2>/dev/null || echo "")
    local text_result
    text_result=$(echo "$result_response" | jq -r '.data.text_result // .text_result // ""' 2>/dev/null || echo "")

    # text_result 可能是 Python dict 格式，如 {'node_status': {}, 'Output': {'output': '...'}}
    # 尝试提取 Output.output 中的实际内容
    if echo "$text_result" | grep -q "'Output'" 2>/dev/null; then
        local extracted
        extracted=$(echo "$text_result" | sed "s/'/\"/g" | jq -r '.Output.output // empty' 2>/dev/null || echo "")
        if [[ -n "$extracted" ]]; then
            text_result="$extracted"
        fi
    fi

    # 生成归档目录名
    local archive_name
    archive_name=$(generate_archive_name "${query:-法律研究}")
    local task_archive_dir="${ARCHIVE_DIR}/${archive_name}"
    mkdir -p "$task_archive_dir"

    # 保存结果为 Markdown（使用主题命名）
    local result_filename="${archive_name}.md"
    local result_file="${task_archive_dir}/${result_filename}"
    {
        echo "# 法律研究报告"
        echo ""
        echo "**任务ID**: ${task_id}"
        echo "**归档时间**: $(date '+%Y-%m-%d %H:%M:%S')"
        echo ""
        echo "## 研究问题"
        echo ""
        echo "${query:-未知问题}"
        echo ""
        echo "---"
        echo ""
        echo "## 研究结果"
        echo ""
        echo "${text_result}"
    } > "$result_file"

    local files=("${result_filename}")

    # 尝试下载报告（报告 API 返回 code 200 表示有报告，404 表示无）
    local report_response
    report_response=$(get_report "$task_id" 2>/dev/null || echo '{"code":404}')
    local report_code
    report_code=$(echo "$report_response" | jq -r '.code // 404' 2>/dev/null)

    if [[ "$report_code" == "200" ]]; then
        local report_url
        report_url=$(echo "$report_response" | jq -r '.data.report_url // empty' 2>/dev/null)

        # 生成统一的报告文件名（与 Markdown 保持一致）
        local report_filename
        report_filename=$(generate_report_filename "${query:-法律研究}")
        local docx_file="${task_archive_dir}/${report_filename%.md}.docx"

        if [[ -n "$report_url" ]]; then
            echo "📥 正在下载报告: ${report_filename%.md}.docx"
            if curl -sL "$report_url" -o "$docx_file" 2>/dev/null && [[ -s "$docx_file" ]]; then
                echo "   - ${report_filename%.md}.docx (详细报告)"
                files+=("${report_filename%.md}.docx")

                # 尝试转换为 Markdown（使用 _报告 后缀避免覆盖文字结果）
                local report_md_name="${report_filename%.md}_报告.md"
                local report_md="${task_archive_dir}/${report_md_name}"
                if convert_docx_to_md "$docx_file" "$report_md"; then
                    echo "   - ${report_md_name} (Markdown版本)"
                    files+=("${report_md_name}")
                fi
            else
                echo "⚠️ 报告下载失败，链接已保存到归档文件"
                echo "" >> "$result_file"
                echo "## 报告下载链接" >> "$result_file"
                echo "" >> "$result_file"
                echo "[报告](${report_url})" >> "$result_file"
            fi
        fi
    else
        echo "ℹ️ 该任务无 Word 报告（仅文字结果）"
    fi

    echo ""
    echo "✅ 归档完成: ${task_archive_dir}/"
    for f in "${files[@]}"; do
        echo "   - ${f}"
    done

    # 返回 JSON 结果
    local files_json
    files_json=$(printf '%s\n' "${files[@]}" | jq -R . | jq -s .)
    echo ""
    echo "{\"code\": 200, \"archive_path\": \"${task_archive_dir}\", \"archive_name\": \"${archive_name}\", \"files\": ${files_json}}"
}

# 列出归档
list_archive() {
    if [[ ! -d "$ARCHIVE_DIR" ]]; then
        echo "[]"
        return 0
    fi

    local result="["
    local first=true

    for dir in "$ARCHIVE_DIR"/*/; do
        if [[ -d "$dir" ]]; then
            local task_id
            task_id=$(basename "$dir")

            if [[ "$first" == "true" ]]; then
                first=false
            else
                result+=","
            fi

            result+="{\"task_id\":\"${task_id}\",\"path\":\"${dir}\"}"
        fi
    done

    result+="]"
    echo "$result"
}

# 主入口
case "${1:-}" in
    submit)
        submit_query "$2"
        ;;
    status)
        get_status "$2"
        ;;
    result)
        get_result "$2"
        ;;
    report)
        get_report "$2"
        ;;
    history)
        get_history "$2" "$3"
        ;;
    wait)
        wait_for_completion "$2" "$3" "$4"
        ;;
    full)
        get_full_result "$2"
        ;;
    archive)
        archive_result "$2"
        ;;
    list-archive)
        list_archive
        ;;
    *)
        echo "用法: $0 <command> [args]"
        echo ""
        echo "命令:"
        echo "  submit \"<query>\"        提交法律研究问题"
        echo "  status <task_id>        查询任务状态"
        echo "  result <task_id>        获取文字分析结果"
        echo "  report <task_id>        获取报告下载链接"
        echo "  history [page] [size]   查看历史任务"
        echo "  wait <task_id> [timeout] [interval]  轮询等待完成"
        echo "  full <task_id>          一键获取完整结果（需任务已完成）"
        echo "  archive <task_id>       归档研究结果到 archive/"
        echo "  list-archive            列出所有归档"
        echo ""
        echo "归档目录结构:"
        echo "  archive/YYMMDD 主题_法律研究报告/"
        echo "    ├── ...研究报告.md         - 研究结果摘要"
        echo "    ├── ...研究报告.docx       - Word 报告（如有）"
        echo "    ├── ...研究报告_报告.md    - Word 转 Markdown（如有）"
        echo "    └── media/                  - 报告图片（如有）"
        echo ""
        echo "示例:"
        echo "  $0 submit \"劳动合同到期不续签需要赔偿吗？\""
        echo "  $0 archive abc123"
        exit 1
        ;;
esac


path: scripts/token-manager.sh
content: #!/bin/bash
# 智合法律研究 - Token 管理脚本
# 用法:
#   ./token-manager.sh show      - 显示当前 Token（脱敏）
#   ./token-manager.sh export    - 导出 Token 到 stdout
#   ./token-manager.sh set <token> - 手动设置 Token
#   ./token-manager.sh clear     - 清除 Token
#   ./token-manager.sh migrate   - 从旧位置迁移 Token

set -e

CONFIG_DIR="${HOME}/.zhihe-legal-research"
ENV_FILE="${CONFIG_DIR}/config"
OLD_ENV_FILE="${HOME}/.openclaw/.env"

# 显示 Token（脱敏）
show_token() {
    if [[ -f "$ENV_FILE" ]]; then
        source "$ENV_FILE" 2>/dev/null || true
        if [[ -n "$LEGAL_RESEARCH_TOKEN" ]]; then
            local masked="${LEGAL_RESEARCH_TOKEN:0:20}...${LEGAL_RESEARCH_TOKEN: -10}"
            echo "[REDACTED_SECRET]"
            echo "来源: ${ENV_FILE}"
        else
            echo "Token: 未设置"
        fi
    else
        echo "[REDACTED_SECRET]"
    fi
}

# 导出 Token
export_token() {
    if [[ -f "$ENV_FILE" ]]; then
        source "$ENV_FILE" 2>/dev/null || true
        if [[ -n "$LEGAL_RESEARCH_TOKEN" ]]; then
            echo "LEGAL_RESEARCH_TOKEN=${LEGAL_RESEARCH_TOKEN}"
        fi
    fi
}

# 手动设置 Token
set_token() {
    local token="$1"

    if [[ -z "$token" ]]; then
        echo "错误: 请提供 Token"
        return 1
    fi

    mkdir -p "$CONFIG_DIR"
    chmod 700 "$CONFIG_DIR"

    if [[ -f "$ENV_FILE" ]] && grep -q "^LEGAL_RESEARCH_TOKEN=" "$ENV_FILE" 2>/dev/null; then
        if [[ "$(uname)" == "Darwin" ]]; then
            sed -i '' "s|^LEGAL_RESEARCH_TOKEN=.*|LEGAL_RESEARCH_TOKEN=${token}|" "$ENV_FILE"
        else
            sed -i "s|^LEGAL_RESEARCH_TOKEN=.*|LEGAL_RESEARCH_TOKEN=${token}|" "$ENV_FILE"
        fi
    else
        echo "LEGAL_RESEARCH_TOKEN=${token}" >> "$ENV_FILE"
    fi

    chmod 600 "$ENV_FILE"
    echo "Token 已保存到: ${ENV_FILE}"
}

# 清除 Token
clear_token() {
    if [[ -f "$ENV_FILE" ]]; then
        rm -f "$ENV_FILE"
        echo "Token 已清除"
    else
        echo "无存储的 Token"
    fi
}

# 从旧位置迁移
migrate_token() {
    if [[ -f "$OLD_ENV_FILE" ]]; then
        source "$OLD_ENV_FILE" 2>/dev/null || true
        if [[ -n "$LEGAL_RESEARCH_TOKEN" ]]; then
            mkdir -p "$CONFIG_DIR"
            chmod 700 "$CONFIG_DIR"
            echo "LEGAL_RESEARCH_TOKEN=${LEGAL_RESEARCH_TOKEN}" > "$ENV_FILE"
            chmod 600 "$ENV_FILE"
            echo "Token 已从 ${OLD_ENV_FILE} 迁移到 ${ENV_FILE}"
        else
            echo "旧配置文件中未找到 Token"
        fi
    else
        echo "旧配置文件不存在: ${OLD_ENV_FILE}"
    fi
}

# 主入口
case "${1:-}" in
    show)
        show_token
        ;;
    export)
        export_token
        ;;
    set)
        set_token "$2"
        ;;
    clear)
        clear_token
        ;;
    migrate)
        migrate_token
        ;;
    *)
        echo "用法: $0 <command> [args]"
        echo ""
        echo "命令:"
        echo "  show          显示当前 Token（脱敏）"
        echo "  export        导出 Token 到 stdout"
        echo "  set <token>   手动设置 Token"
        echo "  clear         清除 Token"
        echo "  migrate       从旧位置 (~/.openclaw/.env) 迁移 Token"
        exit 1
        ;;
esac


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