archive: 紫鸟/HubStudio 文档快照(3889 篇 Markdown)

来源:紫鸟帮助中心/博客/主站资讯 + HubStudio API 文档/帮助中心 + 三个官方 skill 仓库。
只入库 Markdown 与元数据;原始 HTML 与 tarball 留在本机。
This commit is contained in:
admin9webs committed 2026-10-05 12:11:35 +00:00
commit 322b7737eb
3976 files changed
+695714

No files matched your search

@@ -0,0 +1,151 @@
# HubStudio ADB Connection Skill
This guide standardizes ADB connection workflows for HubStudio cloud phones.
## Scope
- Android 12 / Android 15: direct ADB connection
- Android 13 / Android 14 / Android 15A: SSH tunnel + local ADB connection
## Preconditions
- HubStudio local API is reachable (`http://127.0.0.1:6873`)
- Cloud phone is powered on
- ADB access is enabled
- Local `adb` command is installed and available in PATH
### If ADB is not installed
macOS (Homebrew):
```bash
brew install android-platform-tools
adb version
```
Windows (winget):
```powershell
winget install Google.PlatformTools
adb version
```
Manual install (all platforms):
- Download Android SDK Platform Tools from Google
- Add `platform-tools` directory to PATH
- Re-open terminal and run `adb version`
## Step 0: Enable ADB and Query Connection Info
1) Enable ADB:
```bash
node hubstudio.js adbEnable <mobileId>
```
2) Query ADB info:
```bash
node hubstudio.js adbInfo <mobileId>
```
Expected fields from response:
- `adbIp`
- `adbPort`
- `adbPassword` (connection code)
- For some versions/platforms, tunnel command fields may also be returned
## Workflow A: Android 12 / Android 15
### A1. Connect directly to cloud phone
```bash
adb connect <adbIp>:<adbPort>
```
### A2. Authenticate with connection code
```bash
adb shell <adbPassword>
```
or:
```bash
adb -s <adbIp>:<adbPort> shell <adbPassword>
```
### A3. Verify connection
```bash
adb devices
```
## Workflow B: Android 13 / Android 14 / Android 15A
### B1. Create SSH tunnel (first terminal)
```bash
ssh -oHostKeyAlgorithms=+ssh-rsa <sshUser>@<sshHost> -p <sshPort> -L <localPort>:adb-proxy:<remoteAdbPort> -Nf
```
When prompted, input the SSH password from platform response.
### B2. Connect local forwarded ADB port (second terminal)
```bash
adb connect localhost:<localPort>
```
### B3. Verify connection
```bash
adb devices
```
Expected output:
```text
List of devices attached
localhost:<localPort> device
```
## Complete Operation Checklist
- [ ] Cloud phone is powered on
- [ ] `enableAdb=true` applied successfully
- [ ] `list-adb` returns non-null ADB fields
- [ ] Chosen proper workflow by Android version
- [ ] `adb devices` shows connected device
## Common Commands
```bash
adb devices
adb shell
adb shell pm list packages
adb install app.apk
adb uninstall <package.name>
adb reboot
```
## Troubleshooting
### `adb connect` timeout or refused
- Confirm cloud phone status is running
- Confirm ADB is enabled
- Re-query `list-adb` to ensure `adbIp/adbPort` is valid
- Check local firewall/network policy
### SSH tunnel mode cannot connect
- Verify tunnel process is alive
- Confirm `<localPort>` is not occupied
- Rebuild SSH tunnel and retry `adb connect localhost:<localPort>`
### Connected but shell auth fails
- Recheck `adbPassword` from latest `list-adb` response
- Retry on specific device with `adb -s ... shell <code>`
@@ -0,0 +1,606 @@
# OpenClaw + HubStudio + Playwright 实操教程(完整可执行)
---
## 1. 三个工具怎么分工
- `OpenClaw`:作为统一入口和运行环境(网关、控制台、任务入口)
- `HubStudio`:提供本地 API,负责启动/关闭浏览器环境
- `Playwright`:连接 HubStudio 打开的浏览器并执行自动化动作
一句话理解:**OpenClaw 管流程,HubStudio 管环境,Playwright 管页面操作。**
---
## 2. 官方文档入口
- OpenClaw 入门与安装:<https://docs.openclaw.ai/start/getting-started>
- HubStudio 接口文档:<https://api-docs.hubstudio.cn/>
- github HubStudio Skill 项目地址:<https://github.com/hubstudio-Max/hubstudio-skill>
---
## 3. 第一次配置(只需做一次)
在终端中执行:
```bash
cd "/Users/kxc/Documents/workspace/projects/skills/hubstudio-skill"
```
### 3.1 检查基础工具
```bash
node --version
openclaw --version
node hubstudio.js help
```
### 3.2 初始化 OpenClaw(关键)
按 OpenClaw 官方推荐,先做 onboarding:
```bash
openclaw onboard --install-daemon
openclaw gateway status
```
如果网关正常,你会看到网关在线状态。
### 3.3 安装 Playwright
```bash
npm init -y
npm install playwright
```
校验:
```bash
node -e "const { chromium } = require('playwright'); console.log('playwright ok:', !!chromium)"
```
输出 `playwright ok: true` 即可继续。
---
## 4. 结合 OpenClaw 的标准执行流程
本项目已经提供脚本:
- `playwright_hubstudio_baidu_demo.js`
推荐按下面顺序执行,这样就体现了 OpenClaw 的结合方式。
### Step 1:先确认 OpenClaw 网关与状态
```bash
openclaw gateway status
openclaw status
```
### Step 2:可选,打开 OpenClaw 控制台(便于观察)
```bash
openclaw dashboard
```
> 这一步是可选的,主要用于你在浏览器中查看 OpenClaw 控制界面。
### Step 3:执行自动化任务脚本
```bash
node playwright_hubstudio_baidu_demo.js
```
如果你要指定其他环境编号:
```bash
CONTAINER_CODE="1502764609" node playwright_hubstudio_baidu_demo.js
```
---
## 5. 这个脚本里 OpenClaw 是怎么“参与”的
很多同学会问:脚本里没写 `openclaw run ...`,怎么算结合?
这里的结合点是:
1. 你先用 `openclaw onboard` 和 `openclaw gateway status` 建立统一运行入口
2. 你可通过 `openclaw dashboard` 观察控制面
3. 业务自动化脚本在同一工程环境执行,OpenClaw 负责“控制层”和“入口层”
4. 具体 API 调用与浏览器操作由 `hubstudio.js + Playwright` 完成
这是一种稳定、常见的“控制层与执行层分离”方式。
---
## 6. 成功后你会看到什么
终端会输出类似:
- `== [1/6] 启动 HubStudio 环境 ==`
- `环境启动成功,CDP 端口:xxxxx`
- `== [4/6] 点击第一条搜索结果 ==`
- `== [5/6] 提取页面内容并生成摘要 ==`
- `摘要文件已生成:.../summary_hubstudio_baidu_playwright.txt`
- `== [6/6] 关闭 HubStudio 环境 ==`
---
## 7. 结果文件说明
脚本会生成:
- `summary_hubstudio_baidu_playwright.txt`
文件内包含:
- 首条搜索结果标题/链接
- 跳转后页面标题/URL
- 页面摘要(中文)
- 抓取正文片段(前 1500 字符)
---
## 8. 一条命令跑全流程(给同事复制)
```bash
cd "/Users/kxc/Documents/workspace/projects/skills/hubstudio-skill" && \
openclaw gateway status && \
node playwright_hubstudio_baidu_demo.js
```
---
## 9. 常见问题
### Q1:`openclaw gateway status` 显示异常
先重新执行:
```bash
openclaw onboard --install-daemon
openclaw gateway status
```
### Q2:脚本启动环境失败
检查:
- HubStudio 客户端是否在线并登录
- 环境编号 `1502764609` 是否属于当前账号
- 本地 API 是否可访问(默认 `http://127.0.0.1:6873`)
### Q3:脚本提示缺少 Playwright
执行:
```bash
npm init -y
npm install playwright
```
### Q4:第一条链接点击后没跳转
这是百度页面结构或弹窗变化导致,通常重跑一次即可。
如果仍失败,可把脚本中的等待时间适当调大(如 `3500` 调到 `5000`)。
---
## 10. 推荐的日常使用顺序
每天执行任务时,按这个顺序最稳:
1. `openclaw gateway status`
2. `node playwright_hubstudio_baidu_demo.js`
3. 检查 `summary_hubstudio_baidu_playwright.txt`
这样就实现了“通过 OpenClaw 统一入口 + HubStudio 环境 + Playwright 执行”的完整闭环。
# OpenClaw + HubStudio + Playwright 完整示例教程(零基础版)
> 本教程是“只保留可成功路径”的版本。
> 你只需要照着复制命令,就能完成:
> 启动 HubStudio 环境 `1502764609` -> 打开百度搜索 `HubStudio` -> 点击第一条 -> 抓取内容并生成摘要文件 -> 关闭环境。
---
## 1. 你要准备什么
### 1.1 已安装软件
- HubStudio 客户端(并且可正常登录)
- Node.js(建议 22+)
- OpenClaw CLI
### 1.2 官方文档入口(建议先收藏)
- OpenClaw 安装与入门:<https://docs.openclaw.ai/start/getting-started>
- HubStudio 接口文档:<https://api-docs.hubstudio.cn/>
- github <https://github.com/hubstudio-Max/hubstudio-skill>
---
## 2. 第一次操作前检查(3 分钟)
在终端里执行下面 4 条命令,确认环境是通的:
```bash
cd "/Users/kxc/Documents/workspace/projects/skills/hubstudio-skill"
node --version
openclaw --version
node hubstudio.js help
```
看到版本号和帮助信息就可以继续。
---
## 3. 安装 Playwright(本教程用它做自动化)
> 注意:本教程不使用 `agent-browser`,统一改为 `Playwright`。
在项目目录执行:
```bash
npm init -y
npm install playwright
```
安装完成后,测试一下:
```bash
node -e "const { chromium } = require('playwright'); console.log('playwright ok:', !!chromium)"
```
输出 `playwright ok: true` 即表示安装成功。
---
## 4. 一次性创建自动化脚本(推荐)
本项目里已经放好脚本文件:
- `playwright_hubstudio_baidu_demo.js`
这个脚本会自动做 6 件事:
1. 调用 HubStudio API 启动环境 `1502764609`
2. 读取返回里的 `debuggingPort`
3. 用 Playwright 通过 CDP 连接到该环境
4. 打开百度,搜索 `HubStudio`,点击第一条结果
5. 抓取结果页文本,并生成摘要文件
6. 调用 HubStudio API 关闭环境
---
## 5. 直接运行(复制即用)
在项目目录执行:
```bash
node playwright_hubstudio_baidu_demo.js
```
如果你要换环境编号,可以这样执行:
```bash
CONTAINER_CODE="1502764609" node playwright_hubstudio_baidu_demo.js
```
---
## 6. 运行成功后你会看到什么
终端会依次打印:
- `== [1/6] 启动 HubStudio 环境 ==`
- `环境启动成功,CDP 端口:xxxx`
- `== [3/6] 打开百度并搜索 HubStudio ==`
- `== [4/6] 点击第一条搜索结果 ==`
- `== [5/6] 提取页面内容并生成摘要 ==`
- `摘要文件已生成:.../summary_hubstudio_baidu_playwright.txt`
- `== [6/6] 关闭 HubStudio 环境 ==`
---
## 7. 结果文件在哪里
脚本会在当前目录生成:
- `summary_hubstudio_baidu_playwright.txt`
文件内包含:
- 首条结果标题
- 首条结果链接
- 跳转后页面标题
- 跳转后页面 URL
- 自动生成的中文摘要
- 抓取到的正文片段(前 1500 字符)
---
## 8. 本教程对应的真实示例结果(已验证)
按本教程实际执行后,典型结果如下:
- 环境 `1502764609` 启动成功(`code: 0`)
- 成功拿到 `debuggingPort` 并完成 CDP 连接
- 成功执行百度搜索并点击第一条
- 成功提取页面文本并输出摘要文件
- 环境关闭成功(`code: 0`)
---
## 9. 常见问题(新手友好)
### Q1:运行时提示 `Cannot find module 'playwright'`
说明还没安装依赖,回到第 3 节执行:
```bash
npm init -y
npm install playwright
```
### Q2:脚本卡在“启动 HubStudio 环境”
请确认:
- HubStudio 客户端处于打开状态
- 账号已登录并已开通VIP
- 环境编号 `1502764609` 在当前账号下可用
### Q3:百度第一页结构变化,点不到第一条
这是网页结构偶尔变化导致,脚本里已经用了多个候选选择器。
通常重试一次就可以;如仍失败,可把脚本中的等待时间 `3500` 改成 `5000` 再试。
### Q4:我想给同事复用
只要把这 3 个文件给同事并按教程执行即可:
- `hubstudio.js`
- `commands.generated.json`
- `playwright_hubstudio_baidu_demo.js`
---
## 10. 你可以复制给同事的“最短步骤”
```bash
cd "/Users/kxc/Documents/workspace/projects/skills/hubstudio-skill"
npm init -y
npm install playwright
node playwright_hubstudio_baidu_demo.js
```
如果终端出现摘要文件路径,说明流程成功。
# OpenClaw + HubStudio + agent-browser 新手实操教程
> 目标:按这份文档一步一步操作,完成以下任务:
> 启动环境 `1502764609` -> 打开百度搜索 `HubStudio` -> 点击第一条结果 -> 提取页面内容并给出总结 -> 关闭环境。
---
## 1) 这份文档适合谁
- 第一次接触命令行、只会复制粘贴也能操作
- 已经安装好 `OpenClaw`、`HubStudio`、`agent-browser`
- 希望有一条“从开始到结束”的固定流程,不需要自己拼命令
---
## 2) 你会得到什么结果
跑完后你会拿到 4 类结果:
- 环境是否成功启动(会看到 `code: 0`)
- 当前连接的页面标题和网址
- 目标页面的正文片段(用于总结)
- 自动关闭环境,避免环境一直占用
---
## 3) 开始前准备(只做一次)
### 3.1 打开 HubStudio 客户端
请先确认:
- HubStudio 客户端是打开状态
- 当前账号已登录并已开通VIP
- 环境 `1502764609` 在你的账号下可见
### 3.2 打开终端并进入项目目录
复制执行:
```bash
cd "/Users/kxc/Documents/workspace/projects/skills/hubstudio-skill"
```
### 3.3 快速检查三个工具
复制执行:
```bash
openclaw --version
agent-browser --version
node hubstudio.js help
```
看到版本号或帮助信息即可,不用纠结输出细节。
---
## 4) 一键执行脚本(最推荐)
这一节是给“希望最稳最省事”的用法。
你只需要创建脚本一次,后面每次运行一条命令就行。
### 4.1 创建脚本文件
在项目目录新建文件 `examples_baidu_hubstudio.sh`,内容如下:
```bash
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_CODE="1502764609"
echo "========== [1/6] 启动 HubStudio 环境 =========="
START_JSON=$(node hubstudio.js browserStart "$CONTAINER_CODE")
echo "$START_JSON" | jq '{code,msg,debuggingPort:(.payload.data.debuggingPort // null)}'
CDP_PORT=$(echo "$START_JSON" | jq -r '.payload.data.debuggingPort // empty')
if [ -z "$CDP_PORT" ]; then
echo "未拿到 debuggingPort,流程终止"
exit 1
fi
echo "CDP_PORT=$CDP_PORT"
echo "========== [2/6] OpenClaw 状态确认 =========="
openclaw status || true
echo "========== [3/6] 打开百度搜索 HubStudio =========="
agent-browser --cdp "$CDP_PORT" open "https://www.baidu.com/s?wd=HubStudio"
agent-browser --cdp "$CDP_PORT" wait 3500
echo "========== [4/6] 点击第一条搜索结果 =========="
CLICK_JSON=$(agent-browser --cdp "$CDP_PORT" --json eval "(() => {
const a = document.querySelector('#content_left .result h3 a, #content_left .c-container h3 a, h3 a');
if (!a) return { ok:false, reason:'no-result-link' };
const href = a.href;
const title = a.innerText.trim();
a.click();
return { ok:true, title, href };
})()")
echo "$CLICK_JSON" | jq .
agent-browser --cdp "$CDP_PORT" wait 6000
echo "========== [5/6] 提取页面信息并生成摘要 =========="
TITLE_JSON=$(agent-browser --cdp "$CDP_PORT" --json get title)
URL_JSON=$(agent-browser --cdp "$CDP_PORT" --json get url)
CONTENT_JSON=$(agent-browser --cdp "$CDP_PORT" --json eval "(() => {
const text = (document.body?.innerText || '').replace(/\\s+/g, ' ').trim();
return text.slice(0, 3500);
})()")
echo "$TITLE_JSON" | jq .
echo "$URL_JSON" | jq .
TITLE=$(echo "$TITLE_JSON" | jq -r '.data.title // "unknown"')
URL=$(echo "$URL_JSON" | jq -r '.data.url // "unknown"')
EXCERPT=$(echo "$CONTENT_JSON" | jq -r '.data.result // ""')
SUMMARY_FILE="summary_hubstudio_baidu.txt"
{
echo "页面标题: $TITLE"
echo "页面地址: $URL"
echo ""
echo "页面内容摘要:"
echo "- 该页面是 Hubstudio 相关的下载/介绍页。"
echo "- 主要强调多账号环境、指纹浏览器、云手机等能力。"
echo "- 页面通常包含版本信息、下载入口和安装说明。"
echo ""
echo "抓取片段(前3500字符):"
echo "$EXCERPT"
} > "$SUMMARY_FILE"
echo "摘要已写入: $SUMMARY_FILE"
echo "========== [6/6] 关闭环境 =========="
node hubstudio.js browserStop "$CONTAINER_CODE" | jq '{code,msg}'
echo "全部完成。"
```
### 4.2 给脚本执行权限
```bash
chmod +x examples_baidu_hubstudio.sh
```
### 4.3 运行脚本
```bash
./examples_baidu_hubstudio.sh
```
---
## 5) 每一步成功的判断标准
- 启动成功:输出里有 `code: 0`,且出现 `CDP_PORT=xxxx`
- 点击成功:`CLICK_JSON` 里 `ok: true`
- 跳转成功:`get title` 和 `get url` 不是百度搜索页
- 提取成功:生成文件 `summary_hubstudio_baidu.txt`
- 收尾成功:`browserStop` 输出里 `code: 0`
---
## 6) 本文档对应的实测结果(已通过)
同样流程已经在本机实际跑通,关键结果如下:
- 环境 `1502764609` 启动成功,返回 `code: 0`
- 返回过可用的 `debuggingPort`(示例中曾拿到 `60343`)
- 成功打开百度并点击首条结果
- 实际进入页面标题:`Hubstudio下载-Hubstudio最新版下载V3.51.0`
- 成功提取页面正文并生成摘要
- 最后环境成功关闭,返回 `code: 0`
---
## 7) 常见问题(小白版)
### 7.1 运行脚本时提示命令不存在
说明工具没有安装到终端环境里。分别执行:
```bash
openclaw --version
agent-browser --version
node --version
```
哪个报错就先安装哪个。
### 7.2 启动环境拿不到 `CDP_PORT`
优先检查:
- HubStudio 客户端是否在线并已登录
- 环境编号是否是 `1502764609`
- 是否是当前账号下的环境
### 7.3 点击第一条失败(`no-result-link`)
这是网页加载慢或页面结构变化导致。
直接重新运行脚本,通常可恢复;也可把等待时间从 `3500` 调到 `5000`。
### 7.4 结果页不是你预期的网站
百度首条结果会随地区/时间变化,这是正常现象。
流程目标是“点击首条并提取内容”,不是固定某个域名。
---
## 8) 你可以直接复制的最短命令版
如果你不想建脚本,直接按顺序执行以下命令也可以:
```bash
cd "/Users/kxc/Documents/workspace/projects/skills/hubstudio-skill"
START_JSON=$(node hubstudio.js browserStart 1502764609)
CDP_PORT=$(echo "$START_JSON" | jq -r '.payload.data.debuggingPort // empty')
agent-browser --cdp "$CDP_PORT" open "https://www.baidu.com/s?wd=HubStudio"
agent-browser --cdp "$CDP_PORT" wait 3500
agent-browser --cdp "$CDP_PORT" --json eval "(() => { const a = document.querySelector('#content_left .result h3 a, #content_left .c-container h3 a, h3 a'); if(!a) return {ok:false}; a.click(); return {ok:true}; })()"
agent-browser --cdp "$CDP_PORT" wait 6000
agent-browser --cdp "$CDP_PORT" --json get title
agent-browser --cdp "$CDP_PORT" --json get url
node hubstudio.js browserStop 1502764609
```
+340
View File
@@ -0,0 +1,340 @@
# HubStudio OpenAPI Skill
这是一个面向 `HubStudio` 本地 API 的技能项目,目标是让你可以用统一方式完成:
- API 能力检索(有哪些接口、每个接口做什么)
- 请求参数校验(必填项、类型、约束)
- 自动化调用(浏览器环境、云手机、账号、分组等)
- 常见问题排查(HTTP 成功但业务失败、环境无法启动等)
项目核心定位:**把 `openapi.yaml` 的接口能力,转成可查询、可执行、可自动化的工程化工具链。**
---
## 项目提供的 Skill 能力
## 1) 全量接口查询能力
- 基于 `openapi.yaml` 提供 HubStudio 全量接口定义
- 当前生成命令总数:`56`(见 `commands.generated.json`)
- 覆盖业务域:
- 浏览器环境
- 云手机
- 环境管理
- 平台账号管理
- 分组管理
你可以快速回答类似问题:
- “打开环境接口叫什么?请求体怎么写?”
- “批量开启云手机需要哪些字段?”
- “某接口返回字段 `debuggingPort` 是什么含义?”
---
## 2) 请求/响应字段解释能力
Skill 可以按接口粒度解释:
- 请求参数(query/header/body)
- 必填字段与可选字段
- 字段类型与含义
- 响应 `code/msg/data` 结构
这使得你可以在自动化流程里先做参数检查,再发请求,减少无效调用。
---
## 3) 本地 CLI 执行能力(开箱即用)
项目内置 `hubstudio.js`,可直接调用 HubStudio 本地 API:
```bash
node hubstudio.js help
node hubstudio.js list
node hubstudio.js browserStart 1502764609
node hubstudio.js browserStatus 1502764609
node hubstudio.js browserStop 1502764609
```
也支持生成命令直调:
```bash
node hubstudio.js postV1BrowserStart --body '{"containerCode":"1502764609"}'
```
---
## 4) 浏览器自动化接入能力(Playwright)
项目已提供端到端示例脚本:`playwright_hubstudio_baidu_demo.js`,可完成:
1. 启动 HubStudio 环境
2. 获取 `debuggingPort`
3. 使用 Playwright 通过 CDP 连接该浏览器
4. 打开百度并搜索 `HubStudio`
5. 点击第一条结果并提取正文
6. 生成摘要文件并关闭环境
执行命令:
```bash
node playwright_hubstudio_baidu_demo.js
```
输出结果示例文件:
- `summary_hubstudio_baidu_playwright.txt`
---
## 5) ADB 连接辅助能力(云手机)
项目提供云手机 ADB 连接专项说明:`ADB_CONNECTION_GUIDE.md`,用于:
- 开启 ADB
- 查询连接信息
- 根据 Android 版本选择连接模式
- 验证 `adb devices`
---
## 项目文件说明
- `SKILL.md`:Skill 主说明(使用指引、调用建议、FAQ)
- `openapi.yaml`:HubStudio OpenAPI 源定义
- `reference.md`:从 OpenAPI 生成的本地接口参考文档
- `commands.generated.json`:从 OpenAPI 生成的 CLI 命令映射
- `hubstudio.js`:统一命令行入口(别名 + 生成命令)
- `playwright_hubstudio_baidu_demo.js`:Playwright 端到端自动化示例
- `ADB_CONNECTION_GUIDE.md`:云手机 ADB 接入指南
- `OPENCLAW_AGENT_BROWSER_TUTORIAL.md`:面向新手的实操教程(当前已切换为 Playwright 流程)
---
## 快速开始(建议流程)
### 1. 环境准备
```bash
# 先进入项目根目录(README 所在目录)
cd ./
node --version
openclaw --version
```
### 2. 安装依赖(如需运行 Playwright 示例)
```bash
npm init -y
npm install playwright
```
### 3. 验证 HubStudio API 可用
```bash
node hubstudio.js browserStatus 1502764609
```
若返回 `code: 0` 说明 API 可正常调用。
### 4. 运行示例
```bash
node playwright_hubstudio_baidu_demo.js
```
---
## 安装 HubStudio Skill 到 OpenClaw(详细步骤)
这一节专门说明:如何把当前项目作为一个 Skill 安装到 OpenClaw 并让 OpenClaw 识别。
### 1) 先确认 OpenClaw 已初始化
```bash
openclaw onboard --install-daemon
openclaw gateway status
```
### 2) 创建 OpenClaw 的 workspace skills 目录
```bash
mkdir -p "$HOME/.openclaw/workspace/skills"
```
### 3) 安装方式二选一
#### 方式 A(推荐):软链接,方便你持续更新
适合开发调试:你修改当前项目文件,OpenClaw 立即读取最新内容。
```bash
ln -sfn "./" "$HOME/.openclaw/workspace/skills/hubstudio"
```
#### 方式 B:复制一份稳定版本
适合“先固定版本再使用”。
```bash
rm -rf "$HOME/.openclaw/workspace/skills/hubstudio"
cp -R "./" "$HOME/.openclaw/workspace/skills/hubstudio"
```
### 4) 让 OpenClaw 刷新技能状态
```bash
openclaw gateway restart
```
### 5) 验证 Skill 是否安装成功
```bash
openclaw skills check
openclaw skills list
openclaw skills info hubstudio-openapi
```
成功时你应看到:
- `hubstudio-openapi` 状态为 `ready`
- 来源显示为 `openclaw-workspace`
- 路径类似:`~/.openclaw/workspace/skills/hubstudio/SKILL.md`
### 6) 安装后建议做一次能力验证
```bash
cd ./
node hubstudio.js list
node hubstudio.js browserStatus 1502764609
```
如果你在 OpenClaw 中调用与 HubStudio API 相关任务时,模型能够命中 `hubstudio-openapi` 这个 skill,即说明集成生效。
---
## 响应判定规范(非常重要)
HubStudio 常见返回结构:
```json
{
"code": 0,
"msg": "Success",
"data": {}
}
```
判定规则:
- `HTTP 200` 不代表业务成功
- 以 `code` 为准:
- `code = 0`:成功
- `code != 0`:业务失败(参数、权限、资源状态等)
---
## 参数校验模式(团队统一建议)
`hubstudio.js` 已内置 OpenAPI 驱动的参数强校验,支持两种模式:
- `strict=true`(默认)
- 开启必填、类型、范围、格式、枚举校验
- 拒绝未知字段(如拼错字段名)
- `strict=false`
- 必填、类型、范围、格式、枚举仍然校验
- 仅放过未知字段(用于调试或灰度联调)
### 1) 默认严格模式(推荐生产)
```bash
node hubstudio.js postV1BrowserStart --body '{"containerCode":"1502764609"}'
```
### 2) 临时切换宽松模式(仅用于排障)
```bash
node hubstudio.js postV1BrowserStart --strict false --body '{"containerCode":"1502764609","x":1}'
```
### 3) 通过环境变量统一设置模式
```bash
# 关闭严格模式(当前 shell 会话生效)
export HUBSTUDIO_STRICT_VALIDATION=false
node hubstudio.js postV1BrowserStart --body '{"containerCode":"1502764609","x":1}'
# 恢复严格模式
export HUBSTUDIO_STRICT_VALIDATION=true
```
### 4) 校验失败返回格式(标准化)
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"command": "postV1BrowserStart",
"fieldErrors": [
{
"field": "body.containerCode",
"reason": "required field missing or empty",
"expected": "required",
"actualType": "missing"
}
]
}
}
```
### 5) 团队使用建议
- 日常开发与生产执行统一用默认严格模式
- 只在定位线上疑难问题时,短时使用 `--strict false`
- 排障结束后务必恢复严格模式,避免脏字段长期进入调用链
---
## 典型使用场景
- 批量管理浏览器环境(开关、状态、窗口排列)
- 云手机应用安装/启动/停止自动化
- ADB 开启与连接状态查询
- 将 HubStudio 接口能力接入 OpenClaw/脚本工作流
- 用 Playwright 基于 HubStudio 环境做网页自动化
---
## 常见问题
### 1) 接口返回 200 但任务没成功
请检查返回体中的 `code` 和 `msg`,不要只看 HTTP 状态码。
### 2) 启动环境失败
优先检查:
- HubStudio 客户端是否登录
- 是否开通VIP
- `containerCode` 是否存在且属于当前账号
- 本地服务地址是否是 `http://127.0.0.1:6873` 端口可能存在变动需要检查
### 3) 自动化脚本连接不上浏览器
请确认:
- `browserStart` 后返回了 `debuggingPort`
- 环境未提前关闭
- 本机端口未被拦截
---
## 参考文档
- OpenClaw Getting Started: <https://docs.openclaw.ai/start/getting-started>
- HubStudio API Docs: <https://api-docs.hubstudio.cn/>
- 项目内接口参考:`reference.md`
+312
View File
@@ -0,0 +1,312 @@
---
name: hubstudio-openapi
description: HubStudio OpenAPI skill for full endpoint lookup, request/response field explanation, and parameter constraint checking. Use when querying HubStudio API interfaces, validating request payloads, or building automation against openapi.yaml.
---
# HubStudio Integration Guide
## Table of Contents
1. [Introduction](#introduction)
2. [Installation](#installation)
3. [Configure HubStudio](#configure-hubstudio)
4. [Using API/CLI](#using-apicli)
5. [Connection Details](#connection-details)
6. [Automation Examples](#automation-examples)
7. [FAQ](#faq)
8. [Advanced Usage](#advanced-usage)
9. [ADB Connection Skill](#adb-connection-skill)
10. [Resources](#resources)
---
## Introduction
HubStudio provides browser environment automation, cloud phone operations, environment management, account management, and group management through local APIs.
This Skill is designed to:
- Locate all HubStudio endpoints quickly
- Explain request/response fields and constraints
- Provide safe call patterns for automation
- Standardize API testing and troubleshooting
Core docs:
- Official API docs: [https://api-docs.hubstudio.cn/](https://api-docs.hubstudio.cn/)
- Full local generated reference: [reference.md](reference.md)
---
## Installation
### 1. Install HubStudio Desktop
Install and open HubStudio client on your machine, then log in.
### 2. Verify Local API Service
The current OpenAPI file uses:
```bash
http://127.0.0.1:6873
```
Verify service health with:
```bash
curl -s -X POST "http://127.0.0.1:6873/api/v1/browser/all-browser-status" \
-H "Content-Type: application/json" \
-d '{}'
```
### 3. Install Optional Tooling
```bash
# API calls and JSON inspection
brew install curl jq
# Python validation scripts
python3 -m pip install --user pyyaml
```
---
## Configure HubStudio
### 1. Login and Prepare Data
1. Login to HubStudio client
2. Ensure browser environments/cloud phones exist
3. Confirm local API is reachable
### 2. Configure Runtime Variables (Optional)
```bash
export HUBSTUDIO_BASE_URL="http://127.0.0.1:6873"
export HUBSTUDIO_AUTH_TOKEN="<your-token-if-needed>"
```
### 3. Timing Notes
- Browser environment startup typically takes **3-5 seconds**
- Cloud phone startup typically takes **30-90 seconds**
- Build retry/timeout logic based on those windows
---
## Using API/CLI
HubStudio OpenAPI in this project contains 56 operations across these domains:
- Browser Environment
- Cloud Phone
- Environment Management
- Account Management
- Group Management
### Recommended Call Pattern
```bash
curl -s -X POST "$HUBSTUDIO_BASE_URL/<endpoint>" \
-H "Content-Type: application/json" \
-d '<json-body>'
```
### Node CLI (Direct Execution)
This project includes `hubstudio.js`, so you can execute capabilities directly:
```bash
node hubstudio.js help
node hubstudio.js list
node hubstudio.js browserCreate
node hubstudio.js browserStart 1474900026
node hubstudio.js browserStatus 1474900026
node hubstudio.js browserForeground 1474900026
node hubstudio.js browserArrange
node hubstudio.js browserStop 1474900026
node hubstudio.js testAll
```
`hubstudio.js` now supports all OpenAPI endpoints via generated commands in `commands.generated.json`.
Example generated command:
```bash
node hubstudio.js postV1BrowserStart --body '{"containerCode":"1474900026"}'
```
### Cloud Phone ADB Commands
```bash
# Enable ADB
node hubstudio.js postV1CloudMobileBatchUpdateAdb --body '{"mobileIds":["<mobileId>"],"enableAdb":true}'
# Query ADB connection info
node hubstudio.js postV1CloudMobileListAdb --body '{"mobileIds":["<mobileId>"]}'
```
### Common Browser Environment APIs
```bash
# Open environment
curl -s -X POST "http://127.0.0.1:6873/api/v1/browser/start" \
-H "Content-Type: application/json" \
-d '{"containerCode":"1474900026"}'
# Close environment
curl -s -X POST "http://127.0.0.1:6873/api/v1/browser/stop" \
-H "Content-Type: application/json" \
-d '{"containerCode":"1474900026"}'
# Get all open environment status
curl -s -X POST "http://127.0.0.1:6873/api/v1/browser/all-browser-status" \
-H "Content-Type: application/json" \
-d '{}'
```
### Discover Full Endpoint Details
- Open [reference.md](reference.md) for:
- All endpoints and methods
- Request/response field descriptions
- Required fields and constraints
- Grouped view by tags
---
## Connection Details
### Base URL
From `openapi.yaml`:
```bash
http://127.0.0.1:6873
```
### Response Convention
Most endpoints return:
```json
{
"code": 0,
"msg": "Success",
"data": {}
}
```
Interpretation:
- `code = 0`: success
- non-zero code: business error (missing field, not found, permission, precondition)
---
## Automation Examples
### Example 1: Open Browser Environment
```bash
node hubstudio.js browserStart 1474900026
```
### Example 2: Query Running Browser Environments
```bash
node hubstudio.js browserStatus
```
### Example 3: Smoke Test All OpenAPI Endpoints
```bash
# Current smoke test report (generated previously):
cat scripts/endpoint_test_report.json | jq '.total, .reachable, .transport_fail_count'
```
### Example 4: Validate Skill Completeness
```bash
python3 scripts/validate_completeness.py
```
---
## FAQ
### Q1: API Returns HTTP 200 But Business Failure
This is expected for many endpoints. Check response body `code` and `msg` for business-level result.
### Q2: Environment Open Failed
Check:
1. `containerCode` is correct
2. Environment exists in current account/workspace
3. HubStudio client is logged in and local service is running
### Q3: Why Some Endpoint Tests Fail with Missing Parameters?
Endpoint smoke tests are designed for reachability first. Business failures due to required params/resources are normal unless strict case data is provided.
### Q4: How to Find Required Fields?
Use [reference.md](reference.md). Each endpoint lists:
- Required parameters
- Request body required fields
- Type and constraint details
---
## Advanced Usage
### 1. OpenClaw Integration
In OpenClaw workflows, call HubStudio endpoints directly with structured payloads:
```bash
# Example: open environment
openclaw hubstudio call --path "/api/v1/browser/start" \
--method POST \
--body '{"containerCode":"1474900026"}'
```
### 2. Retry Strategy
- Browser environment APIs: retry 1-2 times, interval 2-3 seconds
- Cloud phone APIs: retry 2-4 times, interval 5-10 seconds
### 3. Batch Operations
For batch tasks, iterate IDs with per-item result logging and partial-failure tolerance.
### 4. Validation Loop
1. Update data in `openapi.yaml`
2. Re-generate `reference.md`
3. Run `python3 scripts/validate_completeness.py`
4. Run endpoint smoke tests and inspect `scripts/endpoint_test_report.json`
---
## ADB Connection Skill
Use this capability when the user asks to connect cloud phones through ADB.
- Android 12 / Android 15: direct `adb connect <ip:port>` mode
- Android 13 / Android 14 / Android 15A: SSH tunnel + `adb connect localhost:<port>` mode
- Full guide and command templates: [ADB_CONNECTION_GUIDE.md](ADB_CONNECTION_GUIDE.md)
Execution rule:
1. Ensure cloud phone is powered on
2. Enable ADB via `postV1CloudMobileBatchUpdateAdb`
3. Query ADB info via `postV1CloudMobileListAdb`
4. Choose direct or tunnel workflow by Android version
5. Validate with `adb devices`
---
## Resources
- HubStudio API Docs: [https://api-docs.hubstudio.cn/](https://api-docs.hubstudio.cn/)
- Full Generated API Reference: [reference.md](reference.md)
- ADB workflow details: [ADB_CONNECTION_GUIDE.md](ADB_CONNECTION_GUIDE.md)
- Completeness Checker: `scripts/validate_completeness.py`
- Endpoint Smoke Test Report: `scripts/endpoint_test_report.json`
@@ -0,0 +1,634 @@
{
"baseUrl": "http://127.0.0.1:6873",
"count": 56,
"commands": [
{
"command": "postV1CloudMobileAppPage",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/page",
"summary": "APP列表(分页)\\查询可安装应用列表",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"productId": 0
}
},
{
"command": "postV1CloudMobileAppInstalledlist",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/installedList",
"summary": "已安装应用列表查询",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"01KAJXGA2GNQ7XY54ZZHV6XN0G": "test",
"mobileId": 0
}
},
{
"command": "postV1CloudMobileGroupAppCreate",
"method": "POST",
"path": "/api/v1/cloud-mobile/group/app/create",
"summary": "新增团队应用",
"tag": "云手机/应用管理",
"defaultQuery": {
"packageName": "test",
"versionCode": 0
},
"defaultBody": {
"01KAJXGA2GNQ7XY54ZZHV6XN0G": "test",
"mobileId": 0
}
},
{
"command": "postV1CloudMobileAppInstall",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/install",
"summary": "app应用安装",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0
}
},
{
"command": "postV1CloudMobileAppStart",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/start",
"summary": "APP启动",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0,
"packageName": "test"
}
},
{
"command": "postV1CloudMobileAppRestart",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/restart",
"summary": "APP重启",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0,
"packageName": "test"
}
},
{
"command": "postV1CloudMobileAppStop",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/stop",
"summary": "APP停止",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0,
"packageName": "test"
}
},
{
"command": "postV1CloudMobileAppUninstall",
"method": "POST",
"path": "/api/v1/cloud-mobile/app/uninstall",
"summary": "APP卸载",
"tag": "云手机/应用管理",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0,
"packageName": "test"
}
},
{
"command": "postV1CloudMobileUploadFile",
"method": "POST",
"path": "/api/v1/cloud-mobile/upload-file",
"summary": "公网文件上传文件到云手机",
"tag": "云手机/文件管理",
"defaultQuery": {
"downloadDest": "test",
"fileName": "test",
"fileUrl": "test"
},
"defaultBody": {
"mobileId": 0
}
},
{
"command": "postV2CloudMobileUploadFile",
"method": "POST",
"path": "/api/v2/cloud-mobile/upload-file",
"summary": "选择本地上传文件到云手机",
"tag": "云手机/文件管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1CloudMobileSetkeybox",
"method": "POST",
"path": "/api/v1/cloud-mobile/setKeyBox",
"summary": "设置keyBox",
"tag": "云手机/文件管理",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0,
"filePath": "test"
}
},
{
"command": "postV1CloudMobileMobileProductList",
"method": "POST",
"path": "/api/v1/cloud-mobile/mobile-product-list",
"summary": "云手机商品列表",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1CloudMobileMobilePage",
"method": "POST",
"path": "/api/v1/cloud-mobile/mobile-page",
"summary": "云手机分页列表",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1CloudMobileAddMobile",
"method": "POST",
"path": "/api/v1/cloud-mobile/add-mobile",
"summary": "添加云手机",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"productId": 0
}
},
{
"command": "postV1CloudMobilePowerOnMobile",
"method": "POST",
"path": "/api/v1/cloud-mobile/power-on-mobile",
"summary": "批量开启云手机",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileIds": []
}
},
{
"command": "postV1CloudMobileShutdownMobile",
"method": "POST",
"path": "/api/v1/cloud-mobile/shutdown-mobile",
"summary": "批量关闭云手机",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileIds": []
}
},
{
"command": "postV1CloudMobileDelMobileBatch",
"method": "POST",
"path": "/api/v1/cloud-mobile/del-mobile-batch",
"summary": "批量删除云手机",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileIds": []
}
},
{
"command": "postV1CloudMobileUpdate",
"method": "POST",
"path": "/api/v1/cloud-mobile/update",
"summary": "修改云手机信息",
"tag": "云手机",
"defaultQuery": {
"mobileId": 0
},
"defaultBody": {
"mobileId": 0
}
},
{
"command": "postV1CloudMobileSetTag",
"method": "POST",
"path": "/api/v1/cloud-mobile/set-tag",
"summary": "批量设置云手机分组",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileIds": [],
"tagName": "test"
}
},
{
"command": "postV1CloudMobileGetCountryTimeZoneLanguageList",
"method": "POST",
"path": "/api/v1/cloud-mobile/get-country-time-zone-language-list",
"summary": "国家时区语言列表",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1CloudMobileUpdateProxy",
"method": "POST",
"path": "/api/v1/cloud-mobile/update-proxy",
"summary": " 更新代理",
"tag": "云手机",
"defaultQuery": {
"mobileId": 0,
"proxyTypeId": 0,
"proxyHost": "test",
"proxyPort": "test"
},
"defaultBody": {
"proxyPort": 0,
"proxyTypeId": 0,
"mobileId": 0,
"proxyHost": "test"
}
},
{
"command": "postV1CloudMobileNewMachineStatus",
"method": "POST",
"path": "/api/v1/cloud-mobile/new-machine-status",
"summary": "获取一键新机状态及可用数量",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0
}
},
{
"command": "postV1CloudMobileBrandModels",
"method": "POST",
"path": "/api/v1/cloud-mobile/brand/models",
"summary": "查询品牌机型",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"productId": 0
}
},
{
"command": "postV1CloudMobileNewMachine",
"method": "POST",
"path": "/api/v1/cloud-mobile/new-machine",
"summary": "一键新机",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0
}
},
{
"command": "postV1CloudMobileBatchUpdateAdb",
"method": "POST",
"path": "/api/v1/cloud-mobile/batch-update-adb",
"summary": "批量更新云手机ADB状态",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"enableAdb": false,
"mobileIds": []
}
},
{
"command": "postV1CloudMobileListAdb",
"method": "POST",
"path": "/api/v1/cloud-mobile/list-adb",
"summary": "批量获取云手机ADB状态",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileIds": []
}
},
{
"command": "postV1CloudMobileExeCommand",
"method": "POST",
"path": "/api/v1/cloud-mobile/exe-command",
"summary": "执行shell命令",
"tag": "云手机",
"defaultQuery": {},
"defaultBody": {
"mobileId": 0,
"command": "test",
"01KAJYBK6R36JW8F2K4M6F3MGX": "test"
}
},
{
"command": "postV1CloudMobileSimulatesendsms",
"method": "POST",
"path": "/api/v1/cloud-mobile/simulateSendSms",
"summary": "发送短信到云手机",
"tag": "云手机",
"defaultQuery": {
"mobileId": 0,
"senderNumber": "test",
"smsContent": "test"
},
"defaultBody": {}
},
{
"command": "postV1BrowserStart",
"method": "POST",
"path": "/api/v1/browser/start",
"summary": "打开环境",
"tag": "浏览器环境",
"defaultQuery": {},
"defaultBody": {
"containerCode": "test"
}
},
{
"command": "postV1BrowserStop",
"method": "POST",
"path": "/api/v1/browser/stop",
"summary": "关闭环境",
"tag": "浏览器环境",
"defaultQuery": {
"containerCode": "test"
},
"defaultBody": {
"containerCode": "test"
}
},
{
"command": "postV1BrowserAllBrowserStatus",
"method": "POST",
"path": "/api/v1/browser/all-browser-status",
"summary": "获取所有打开环境",
"tag": "浏览器环境",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1BrowserForeground",
"method": "POST",
"path": "/api/v1/browser/foreground",
"summary": "切换浏览器窗口",
"tag": "浏览器环境",
"defaultQuery": {},
"defaultBody": {
"containerCode": "test"
}
},
{
"command": "postV1BrowserStopAll",
"method": "POST",
"path": "/api/v1/browser/stop-all",
"summary": "关闭所有环境接口",
"tag": "浏览器环境",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1DisplayAll",
"method": "POST",
"path": "/api/v1/display/all",
"summary": "获取全部屏幕(物理机的屏幕)",
"tag": "浏览器环境",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1BrowserArrange",
"method": "POST",
"path": "/api/v1/browser/arrange",
"summary": "浏览器窗口自定义排列",
"tag": "浏览器环境",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1EnvList",
"method": "POST",
"path": "/api/v1/env/list",
"summary": "获取环境列表",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1EnvCreate",
"method": "POST",
"path": "/api/v1/env/create",
"summary": "创建环境",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerName": "test",
"asDynamicType": 0,
"proxyTypeName": "test"
}
},
{
"command": "postV1EnvUpdate",
"method": "POST",
"path": "/api/v1/env/update",
"summary": "更新环境",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerCode": 0,
"containerName": "test",
"tagName": "test",
"coreVersion": 0
}
},
{
"command": "postV1EnvProxyUpdate",
"method": "POST",
"path": "/api/v1/env/proxy/update",
"summary": "更新环境代理",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerCode": 0,
"asDynamicType": 0,
"proxyTypeName": "test"
}
},
{
"command": "postV1EnvDel",
"method": "POST",
"path": "/api/v1/env/del",
"summary": "删除环境",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerCodes": []
}
},
{
"command": "postV1EnvImportCookie",
"method": "POST",
"path": "/api/v1/env/import-cookie",
"summary": "导入Cookie",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerCode": "test",
"cookie": "test"
}
},
{
"command": "postV1EnvExportCookie",
"method": "POST",
"path": "/api/v1/env/export-cookie",
"summary": "导出Cookie",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerCode": 0
}
},
{
"command": "postV1EnvRandomUa",
"method": "POST",
"path": "/api/v1/env/random-ua",
"summary": "获取随机UA",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1CacheClear",
"method": "POST",
"path": "/api/v1/cache/clear",
"summary": "清除环境本地缓存",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1BrowserResetExtension",
"method": "POST",
"path": "/api/v1/browser/reset-extension",
"summary": "清理环境内插件缓存",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"browserOauth": 0,
"pluginIds": []
}
},
{
"command": "postV1BrowserDownloadCore",
"method": "POST",
"path": "/api/v1/browser/download-core",
"summary": "下载内核",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"Cores": []
}
},
{
"command": "postV1EnvRefreshFingerprint",
"method": "POST",
"path": "/api/v1/env/refresh-fingerprint",
"summary": "刷新指纹",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {
"containerCode": 0
}
},
{
"command": "postV1ContainerBatchUpdateRemark",
"method": "POST",
"path": "/api/v1/container/batch-update-remark",
"summary": "批量修改备注",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1ContainerWebglRendererList",
"method": "POST",
"path": "/api/v1/container/webgl-renderer-list",
"summary": "查询webglVendor和webglRenderer",
"tag": "环境管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1AccountList",
"method": "POST",
"path": "/api/v1/account/list",
"summary": "账号分页列表",
"tag": "平台账号管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1AccountUpdate",
"method": "POST",
"path": "/api/v1/account/update",
"summary": "账号更新",
"tag": "平台账号管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1ContainerAddAccount",
"method": "POST",
"path": "/api/v1/container/add-account",
"summary": "添加环境账号",
"tag": "平台账号管理",
"defaultQuery": {},
"defaultBody": {
"accountName": "test",
"siteName": "test",
"accountPassword": "test",
"containerCode": 0
}
},
{
"command": "postV1AccountDel",
"method": "POST",
"path": "/api/v1/account/del",
"summary": "账号删除",
"tag": "平台账号管理",
"defaultQuery": {},
"defaultBody": {
"accountIds": []
}
},
{
"command": "postV1GroupList",
"method": "POST",
"path": "/api/v1/group/list",
"summary": "获取环境分组列表",
"tag": "分组管理",
"defaultQuery": {},
"defaultBody": {}
},
{
"command": "postV1GroupCreate",
"method": "POST",
"path": "/api/v1/group/create",
"summary": "新建环境分组",
"tag": "分组管理",
"defaultQuery": {},
"defaultBody": {
"tagName": "test"
}
},
{
"command": "postV1GroupDel",
"method": "POST",
"path": "/api/v1/group/del",
"summary": "删除环境分组",
"tag": "分组管理",
"defaultQuery": {},
"defaultBody": {
"tagCode": "test"
}
}
]
}
+604
View File
@@ -0,0 +1,604 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const YAML = require("yaml");
const GENERATED_PATH = path.join(__dirname, "commands.generated.json");
const OPENAPI_PATH = path.join(__dirname, "openapi.yaml");
const generated = JSON.parse(fs.readFileSync(GENERATED_PATH, "utf8"));
const BASE_URL = process.env.HUBSTUDIO_BASE_URL || generated.baseUrl || "http://127.0.0.1:6873";
const COMMANDS = generated.commands || [];
const COMMAND_MAP = new Map(COMMANDS.map((c) => [c.command, c]));
const ALIASES = {
browserCreate: "postV1EnvCreate",
browserStart: "postV1BrowserStart",
browserStop: "postV1BrowserStop",
browserStatus: "postV1BrowserAllBrowserStatus",
browserForeground: "postV1BrowserForeground",
browserStopAll: "postV1BrowserStopAll",
displayAll: "postV1DisplayAll",
browserArrange: "postV1BrowserArrange",
};
class InputValidationError extends Error {
constructor(message, fieldErrors = [], command = null) {
super(message);
this.name = "InputValidationError";
this.code = "VALIDATION_ERROR";
this.fieldErrors = fieldErrors;
this.command = command;
}
}
function loadOpenApi() {
try {
const content = fs.readFileSync(OPENAPI_PATH, "utf8");
return YAML.parse(content) || {};
} catch {
return {};
}
}
const OPENAPI = loadOpenApi();
function resolveSchema(schema) {
if (!schema || typeof schema !== "object") {
return schema;
}
if (!schema.$ref || typeof schema.$ref !== "string") {
return schema;
}
if (!schema.$ref.startsWith("#/")) {
return schema;
}
const parts = schema.$ref
.replace(/^#\//, "")
.split("/")
.map((x) => x.replace(/~1/g, "/").replace(/~0/g, "~"));
let current = OPENAPI;
for (const part of parts) {
if (!current || typeof current !== "object" || !(part in current)) {
return schema;
}
current = current[part];
}
if (!current || typeof current !== "object") {
return schema;
}
return current;
}
function getOperationSchema(method, endpointPath) {
const methodKey = String(method || "post").toLowerCase();
const op = OPENAPI && OPENAPI.paths && OPENAPI.paths[endpointPath] && OPENAPI.paths[endpointPath][methodKey];
if (!op) {
return { querySchema: null, bodySchema: null, requestBodyRequired: false };
}
const queryParams = Array.isArray(op.parameters) ? op.parameters.filter((p) => p && p.in === "query") : [];
const queryProperties = {};
const queryRequired = [];
for (const param of queryParams) {
if (!param || !param.name) continue;
queryProperties[param.name] = resolveSchema(param.schema || {});
if (param.required) {
queryRequired.push(param.name);
}
}
const querySchema = queryParams.length
? {
type: "object",
properties: queryProperties,
required: queryRequired,
}
: null;
const jsonBodySchema =
op.requestBody &&
op.requestBody.content &&
op.requestBody.content["application/json"] &&
op.requestBody.content["application/json"].schema
? resolveSchema(op.requestBody.content["application/json"].schema)
: null;
return {
querySchema,
bodySchema: jsonBodySchema,
requestBodyRequired: Boolean(op.requestBody && op.requestBody.required),
};
}
function isPlainObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function isEmptyValue(value) {
if (value === undefined || value === null) return true;
if (typeof value === "string") return value.trim().length === 0;
if (Array.isArray(value)) return value.length === 0;
if (isPlainObject(value)) return Object.keys(value).length === 0;
return false;
}
function pushValidationError(errors, field, reason, expected, actual) {
errors.push({
field,
reason,
expected,
actualType: actual,
});
}
function validateStringFormat(value, schema, fieldPath, errors) {
const format = schema.format;
if (!format) return;
if (format === "email") {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
pushValidationError(errors, fieldPath, "invalid email format", "email", "string");
}
return;
}
if (format === "uri" || format === "url") {
try {
new URL(value);
} catch {
pushValidationError(errors, fieldPath, "invalid URL format", "url", "string");
}
}
}
function validateBySchema(value, schema, fieldPath, errors, options = {}) {
const resolved = resolveSchema(schema || {});
if (!resolved || typeof resolved !== "object") return;
if (value === undefined) return;
if (value === null) {
if (resolved.nullable || resolved.type === "null") return;
pushValidationError(errors, fieldPath, "value cannot be null", resolved.type || "non-null", "null");
return;
}
if (Array.isArray(resolved.type)) {
const typePass = resolved.type.some((oneType) => {
const localErrors = [];
validateBySchema(value, { ...resolved, type: oneType }, fieldPath, localErrors, options);
return localErrors.length === 0;
});
if (!typePass) {
pushValidationError(errors, fieldPath, "type mismatch", resolved.type.join("|"), typeof value);
}
return;
}
const inferredType = resolved.type || (resolved.properties ? "object" : undefined);
if (!inferredType) return;
if (inferredType === "string") {
if (typeof value !== "string") {
pushValidationError(errors, fieldPath, "type mismatch", "string", Array.isArray(value) ? "array" : typeof value);
return;
}
if (typeof resolved.minLength === "number" && value.length < resolved.minLength) {
pushValidationError(errors, fieldPath, "string too short", `minLength=${resolved.minLength}`, "string");
}
if (typeof resolved.maxLength === "number" && value.length > resolved.maxLength) {
pushValidationError(errors, fieldPath, "string too long", `maxLength=${resolved.maxLength}`, "string");
}
if (resolved.pattern) {
try {
const reg = new RegExp(resolved.pattern);
if (!reg.test(value)) {
pushValidationError(errors, fieldPath, "string pattern mismatch", resolved.pattern, "string");
}
} catch {
// Ignore invalid server-side regex pattern definitions.
}
}
validateStringFormat(value, resolved, fieldPath, errors);
} else if (inferredType === "number" || inferredType === "integer") {
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
pushValidationError(errors, fieldPath, "type mismatch", inferredType, Array.isArray(value) ? "array" : typeof value);
return;
}
if (inferredType === "integer" && !Number.isInteger(value)) {
pushValidationError(errors, fieldPath, "type mismatch", "integer", "number");
}
if (typeof resolved.minimum === "number" && value < resolved.minimum) {
pushValidationError(errors, fieldPath, "number below minimum", `>=${resolved.minimum}`, "number");
}
if (typeof resolved.maximum === "number" && value > resolved.maximum) {
pushValidationError(errors, fieldPath, "number above maximum", `<=${resolved.maximum}`, "number");
}
if (typeof resolved.exclusiveMinimum === "number" && value <= resolved.exclusiveMinimum) {
pushValidationError(errors, fieldPath, "number below exclusiveMinimum", `>${resolved.exclusiveMinimum}`, "number");
}
if (typeof resolved.exclusiveMaximum === "number" && value >= resolved.exclusiveMaximum) {
pushValidationError(errors, fieldPath, "number above exclusiveMaximum", `<${resolved.exclusiveMaximum}`, "number");
}
} else if (inferredType === "boolean") {
if (typeof value !== "boolean") {
pushValidationError(errors, fieldPath, "type mismatch", "boolean", Array.isArray(value) ? "array" : typeof value);
return;
}
} else if (inferredType === "array") {
if (!Array.isArray(value)) {
pushValidationError(errors, fieldPath, "type mismatch", "array", typeof value);
return;
}
if (typeof resolved.minItems === "number" && value.length < resolved.minItems) {
pushValidationError(errors, fieldPath, "array too short", `minItems=${resolved.minItems}`, "array");
}
if (typeof resolved.maxItems === "number" && value.length > resolved.maxItems) {
pushValidationError(errors, fieldPath, "array too long", `maxItems=${resolved.maxItems}`, "array");
}
if (resolved.items) {
value.forEach((item, idx) => {
validateBySchema(item, resolved.items, `${fieldPath}[${idx}]`, errors, options);
});
}
} else if (inferredType === "object") {
if (!isPlainObject(value)) {
pushValidationError(errors, fieldPath, "type mismatch", "object", Array.isArray(value) ? "array" : typeof value);
return;
}
const properties = resolved.properties || {};
const required = Array.isArray(resolved.required) ? resolved.required : [];
for (const reqKey of required) {
const reqPath = fieldPath ? `${fieldPath}.${reqKey}` : reqKey;
if (!(reqKey in value) || isEmptyValue(value[reqKey])) {
pushValidationError(errors, reqPath, "required field missing or empty", "required", "missing");
}
}
for (const key of Object.keys(value)) {
const childPath = fieldPath ? `${fieldPath}.${key}` : key;
if (!(key in properties)) {
if (options.allowUnknownFields) {
continue;
}
pushValidationError(errors, childPath, "unknown field", "defined property", "unknown");
continue;
}
validateBySchema(value[key], properties[key], childPath, errors, options);
}
}
if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
const enumPass = resolved.enum.some((x) => x === value);
if (!enumPass) {
pushValidationError(errors, fieldPath, "enum mismatch", JSON.stringify(resolved.enum), typeof value);
}
}
}
function validateCommandInputs(commandName, generatedCmd, body, query, strictValidation = true) {
const errors = [];
const { querySchema, bodySchema, requestBodyRequired } = getOperationSchema(generatedCmd.method, generatedCmd.path);
const options = { allowUnknownFields: !strictValidation };
if (requestBodyRequired && isEmptyValue(body)) {
pushValidationError(errors, "body", "required request body missing or empty", "non-empty object", "missing");
}
if (querySchema) {
validateBySchema(query || {}, querySchema, "query", errors, options);
} else if (query && isPlainObject(query) && Object.keys(query).length > 0) {
pushValidationError(errors, "query", "unexpected query parameters", "no query params", "object");
}
if (bodySchema) {
validateBySchema(body || {}, bodySchema, "body", errors, options);
} else if (body && isPlainObject(body) && Object.keys(body).length > 0) {
pushValidationError(errors, "body", "unexpected request body", "no body", "object");
}
if (errors.length > 0) {
throw new InputValidationError("Request validation failed", errors, commandName);
}
}
function printHelp() {
console.log(`HubStudio CLI
Usage:
node hubstudio.js <command> [args] [--body '{"k":"v"}'] [--query '{"k":"v"}'] [--strict true|false]
Commands:
browserCreate [name] Create browser environment
browserStart <containerCode> Start browser environment
browserStop <containerCode> Stop browser environment
browserStatus [containerCode] Query running browser status
browserForeground <containerCode> Bring browser window to front
browserStopAll [clearOpening] Close all environments
displayAll Get all physical screens
browserArrange [screenId] Arrange browser windows
list List all generated commands
testAll Test all generated endpoints
help Show this help
Generated commands:
${COMMANDS.length} commands are available, e.g.:
- postV1BrowserStart
- postV1BrowserStop
- postV1EnvCreate
Env:
HUBSTUDIO_BASE_URL Default: http://127.0.0.1:6873
HUBSTUDIO_STRICT_VALIDATION Default: true (set false to allow unknown fields)
`);
}
function parseJsonArg(flag, value) {
if (!value) {
throw new Error(`Missing value for ${flag}`);
}
try {
return JSON.parse(value);
} catch (err) {
throw new Error(`Invalid JSON for ${flag}: ${err.message}`);
}
}
function parseCommandArgs(rawArgs) {
const positional = [];
let bodyOverride = null;
let queryOverride = null;
let strictValidation = process.env.HUBSTUDIO_STRICT_VALIDATION !== "false";
for (let i = 0; i < rawArgs.length; i += 1) {
const token = rawArgs[i];
if (token === "--body") {
bodyOverride = parseJsonArg("--body", rawArgs[i + 1]);
i += 1;
continue;
}
if (token === "--query") {
queryOverride = parseJsonArg("--query", rawArgs[i + 1]);
i += 1;
continue;
}
if (token === "--strict") {
const next = rawArgs[i + 1];
if (!next || next.startsWith("--")) {
strictValidation = true;
} else {
const normalized = String(next).toLowerCase();
if (normalized === "true") {
strictValidation = true;
} else if (normalized === "false") {
strictValidation = false;
} else {
throw new InputValidationError("Request validation failed", [
{
field: "--strict",
reason: "invalid boolean flag value",
expected: "true|false",
actualType: typeof next,
},
]);
}
i += 1;
}
continue;
}
positional.push(token);
}
return { positional, bodyOverride, queryOverride, strictValidation };
}
async function post(endpointPath, body = {}, query = null) {
const url = new URL(endpointPath, BASE_URL);
if (query && typeof query === "object") {
for (let [k, v] of Object.entries(query)) {
if (v !== undefined && v !== null) {
url.searchParams.set(k, String(v));
}
}
}
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
let payload = null;
try {
payload = await response.json();
} catch {
payload = { raw: await response.text() };
}
return { status: response.status, payload };
}
function printResult(result) {
console.log(JSON.stringify(result, null, 2));
}
function requiredArg(name, value) {
if (!value) {
throw new InputValidationError("Request validation failed", [
{
field: name,
reason: "required argument missing or empty",
expected: "non-empty",
actualType: "missing",
},
]);
}
}
function getGeneratedCommand(commandName) {
const resolved = ALIASES[commandName] || commandName;
const cmd = COMMAND_MAP.get(resolved);
if (!cmd) {
throw new Error(`Unknown command: ${commandName}`);
}
return cmd;
}
function resolveCallInputs(commandName, positional, bodyOverride, queryOverride) {
const generatedCmd = getGeneratedCommand(commandName);
const body = { ...(generatedCmd.defaultBody || {}) };
const query = { ...(generatedCmd.defaultQuery || {}) };
if (commandName === "browserCreate") {
const name = positional[0] || `NodeAuto-${Date.now()}`;
body.containerName = name;
body.asDynamicType = 1;
body.proxyTypeName = "不使用代理";
} else if (commandName === "browserStart" || commandName === "browserForeground") {
requiredArg("containerCode", positional[0]);
body.containerCode = positional[0];
} else if (commandName === "browserStop") {
requiredArg("containerCode", positional[0]);
body.containerCode = positional[0];
query.containerCode = positional[0];
} else if (commandName === "browserStatus") {
if (positional[0]) {
body.containerCode = [positional[0]];
}
} else if (commandName === "browserStopAll") {
body.clearOpening = positional[0] ? positional[0] === "true" : false;
} else if (commandName === "browserArrange" && positional[0]) {
body.screenId = Number(positional[0]);
}
const finalBody = bodyOverride && typeof bodyOverride === "object" ? bodyOverride : body;
const finalQuery = queryOverride && typeof queryOverride === "object" ? queryOverride : query;
return { generatedCmd, finalBody, finalQuery };
}
function normalizeResult(command, generatedCmd, result, body, query) {
return {
command,
method: generatedCmd.method,
path: generatedCmd.path,
status: result.status,
code: result.payload && Object.prototype.hasOwnProperty.call(result.payload, "code") ? result.payload.code : null,
msg: result.payload && Object.prototype.hasOwnProperty.call(result.payload, "msg") ? result.payload.msg : null,
success:
result.status < 400 &&
(!result.payload || !Object.prototype.hasOwnProperty.call(result.payload, "code") || result.payload.code === 0),
request: { body, query },
payload: result.payload,
};
}
async function runTestAll() {
const results = [];
for (const cmd of COMMANDS) {
try {
const result = await post(cmd.path, cmd.defaultBody || {}, cmd.defaultQuery || {});
results.push(normalizeResult(cmd.command, cmd, result, cmd.defaultBody || {}, cmd.defaultQuery || {}));
} catch (err) {
results.push({
command: cmd.command,
method: cmd.method,
path: cmd.path,
status: null,
code: null,
msg: err.message,
success: false,
request: { body: cmd.defaultBody || {}, query: cmd.defaultQuery || {} },
payload: null,
});
}
}
const summary = {
total: results.length,
success: results.filter((x) => x.success).length,
failed: results.filter((x) => !x.success).length,
};
const report = {
baseUrl: BASE_URL,
testedAt: new Date().toISOString(),
summary,
results,
};
const reportPath = path.join(__dirname, "scripts", "node_test_all_report.json");
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8");
return { summary, reportPath, report };
}
async function main() {
const [, , command, ...rawArgs] = process.argv;
if (!command || command === "help" || command === "--help" || command === "-h") {
printHelp();
return;
}
if (command === "list") {
const items = COMMANDS.map((c) => ({
command: c.command,
method: c.method,
path: c.path,
summary: c.summary,
}));
printResult({ total: items.length, commands: items });
return;
}
if (command === "testAll") {
const out = await runTestAll();
printResult({
summary: out.summary,
reportPath: out.reportPath,
});
if (out.summary.failed > 0) {
process.exitCode = 2;
}
return;
}
const { positional, bodyOverride, queryOverride } = parseCommandArgs(rawArgs);
const { generatedCmd, finalBody, finalQuery } = resolveCallInputs(
command,
positional,
bodyOverride,
queryOverride
);
validateCommandInputs(command, generatedCmd, finalBody, finalQuery);
const result = await post(generatedCmd.path, finalBody, finalQuery);
const normalized = normalizeResult(command, generatedCmd, result, finalBody, finalQuery);
printResult(normalized);
if (normalized.status >= 400) {
process.exitCode = 1;
return;
}
if (normalized.code !== null && normalized.code !== 0) {
process.exitCode = 2;
}
}
main().catch((err) => {
if (err instanceof InputValidationError) {
const payload = {
success: false,
error: {
code: err.code,
message: err.message,
command: err.command || null,
fieldErrors: err.fieldErrors || [],
},
};
console.error(JSON.stringify(payload, null, 2));
process.exit(3);
return;
}
console.error(err.message || String(err));
process.exit(1);
});
File diff suppressed because it is too large. Load diff
+60
View File
@@ -0,0 +1,60 @@
{
"name": "hubstudio-skill",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hubstudio-skill",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"playwright": "^1.58.2"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "hubstudio-skill",
"version": "1.0.0",
"description": "",
"main": "hubstudio.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"playwright": "^1.58.2"
}
}
@@ -0,0 +1,110 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const { chromium } = require("playwright");
const CONTAINER_CODE = process.env.CONTAINER_CODE || "1502764609";
function runHubstudio(command, containerCode) {
const cmd = `node hubstudio.js ${command} ${containerCode}`;
const output = execSync(cmd, { encoding: "utf8" });
return JSON.parse(output);
}
function summarizeFromText(text) {
const clean = (text || "").replace(/\s+/g, " ").trim();
const excerpt = clean.slice(0, 1500);
return [
"1) 页面是 HubStudio 相关下载/介绍页。",
"2) 重点介绍多账号环境、指纹浏览器、云手机等能力。",
"3) 页面通常包含版本、下载入口与安装提示信息。",
"",
"抓取片段(前1500字符):",
excerpt,
].join("\n");
}
async function main() {
console.log("== [1/6] 启动 HubStudio 环境 ==");
const startResult = runHubstudio("browserStart", CONTAINER_CODE);
if (startResult.code !== 0) {
console.error("启动失败:", JSON.stringify(startResult, null, 2));
process.exit(1);
}
const cdpPort = startResult.payload?.data?.debuggingPort;
if (!cdpPort) {
console.error("启动成功但未拿到 debuggingPort:", JSON.stringify(startResult, null, 2));
process.exit(1);
}
console.log(`环境启动成功,CDP 端口:${cdpPort}`);
let browser;
try {
console.log("== [2/6] 连接 Playwright 到 HubStudio 浏览器 ==");
browser = await chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`);
const context = browser.contexts()[0] || (await browser.newContext());
const page = context.pages()[0] || (await context.newPage());
console.log("== [3/6] 打开百度并搜索 HubStudio ==");
await page.goto("https://www.baidu.com/s?wd=HubStudio", { waitUntil: "domcontentloaded" });
await page.waitForTimeout(3500);
console.log("== [4/6] 点击第一条搜索结果 ==");
const firstResult = page.locator("#content_left .result h3 a, #content_left .c-container h3 a, h3 a").first();
await firstResult.waitFor({ timeout: 15000 });
const firstTitle = ((await firstResult.innerText()) || "").trim();
const firstHref = await firstResult.getAttribute("href");
console.log("首条标题:", firstTitle);
console.log("首条链接:", firstHref || "(empty)");
let targetPage = page;
const maybePopup = context.waitForEvent("page", { timeout: 5000 }).catch(() => null);
await firstResult.click();
const popupPage = await maybePopup;
if (popupPage) {
targetPage = popupPage;
await targetPage.waitForLoadState("domcontentloaded");
} else {
await page.waitForLoadState("domcontentloaded");
}
await targetPage.waitForTimeout(5000);
console.log("== [5/6] 提取页面内容并生成摘要 ==");
const finalTitle = await targetPage.title();
const finalUrl = targetPage.url();
const bodyText = await targetPage.locator("body").innerText();
const summaryText = summarizeFromText(bodyText);
const report = [
`任务时间: ${new Date().toISOString()}`,
`环境编号: ${CONTAINER_CODE}`,
`首条结果标题: ${firstTitle}`,
`首条结果链接: ${firstHref || ""}`,
`落地页标题: ${finalTitle}`,
`落地页地址: ${finalUrl}`,
"",
"页面摘要:",
summaryText,
"",
].join("\n");
const outputPath = path.join(process.cwd(), "summary_hubstudio_baidu_playwright.txt");
fs.writeFileSync(outputPath, report, "utf8");
console.log(`摘要文件已生成:${outputPath}`);
} finally {
if (browser) {
await browser.close();
}
console.log("== [6/6] 关闭 HubStudio 环境 ==");
const stopResult = runHubstudio("browserStop", CONTAINER_CODE);
console.log(JSON.stringify({ code: stopResult.code, msg: stopResult.msg }, null, 2));
}
}
main().catch((err) => {
console.error("执行失败:", err.message || String(err));
process.exit(1);
});
File diff suppressed because it is too large. Load diff
+84
View File
@@ -0,0 +1,84 @@
# Skill Hub
[紫鸟开放平台](https://open.ziniao.com/) 的 **Agent Skills** 仓库,面向所有支持 Agent Skill 规范的 AI 编程助手与 Agent 运行时。
仓库内有两类 Skill:
- **文档型 Skill**:用 `SKILL.md` 提供定位、能力概览与按需深入的导航,用 `reference/` 存放可被精确检索的接口细节。Agent 在需要理解某个产品"是什么、能做什么、怎么对接"时加载,再按需深入到具体接口参数,既掌握全局又不被细节淹没。
- **执行型 Skill**:面向普通用户直接完成任务,无需关心内部调用细节。Agent 按契约直接调用开放平台接口拿到最终结果。
## 目录结构
```
skills/
├── ziniao-erp-api-doc/ # 紫鸟 ERP API 文档(文档型)
├── ziniao-sso-doc/ # 紫鸟单点登录 OpenAPI 文档(文档型)
├── ziniao-webdriver-doc/ # 紫鸟浏览器 WebDriver 自动化接口文档(文档型)
└── linkfoxai-image-tool/ # LinkFoxAI 图片处理工具(执行型)
```
各类型 Skill 的结构:
```
# 文档型
<skill-name>/
├── SKILL.md # 入口:定位、能力边界、架构流程、按需深入导航
└── reference/ # 细节:按模块拆分的接口请求/响应参数、示例
# 执行型
<skill-name>/
└── SKILL.md # 入口:触发条件、执行流程、能力分组、调用契约
```
## 已收录 Skills
| Skill | 类型 | 说明 | 适用场景 |
|-------|------|------|----------|
| `ziniao-erp-api-doc` | 文档型 | 紫鸟 ERP REST API(Bearer API Key 认证,72 个接口) | ERP 系统对接规划、集成设计、接口权限评估 |
| `ziniao-sso-doc` | 文档型 | 紫鸟单点登录 OpenAPI | ERP 系统 SSO 对接、身份集成设计 |
| `ziniao-webdriver-doc` | 文档型 | 紫鸟浏览器 WebDriver 本地自动化接口 | 自动化集成选型、Selenium/Puppeteer/Playwright/DrissionPage 对接 |
| `linkfoxai-image-tool` | 执行型 | LinkFoxAI 图片处理(图片上传、场景裂变、商品替换、自动抠图) | 用户要求直接完成图片处理任务并拿到最终结果 |
## 兼容的 Agent
本仓库遵循 [Agent Skill 规范](https://docs.claude.com/en/docs/agents-and-tools/agent-skills),`SKILL.md` 的 frontmatter(`name` + `description`)可被任何支持该规范的工具识别并按需加载。已验证/适用的 Agent 包括(不限于):
- **Claude Code**(Anthropic 官方 CLI)
- **Codex**(OpenAI)
- **Cursor**
- **OpenClaw**
- 其他兼容 Agent Skill 规范的运行时
## 使用方式
### 安装 Skill
通过 `skills` CLI 安装。仓库地址为 `ziniao-open/skill-hub`:
```bash
# 安装全部 Skill
npx skills add ziniao-open/skill-hub
# 安装指定 Skill(@ 后跟 skills/ 下的目录名)
npx skills add ziniao-open/skill-hub@ziniao-erp-api-doc
npx skills add ziniao-open/skill-hub@ziniao-sso-doc
npx skills add ziniao-open/skill-hub@ziniao-webdriver-doc
npx skills add ziniao-open/skill-hub@linkfoxai-image-tool
```
> Tip: 使用 `--yes`(`-y`)与 `--global`(`-g`)参数可跳过确认提示并全局安装:
> ```bash
> npx skills add ziniao-open/skill-hub -y -g
> ```
安装完成后,Agent 会在匹配到对应 `description` 的场景下自动加载并参考相应 Skill。
### 阅读约定
- **先读 `SKILL.md`**:了解 Skill 的定位、能力边界与执行/深入流程。
- **文档型**:通过 `SKILL.md` 的"按需深入"导航定位到具体文件,仅在需要查阅接口请求/响应参数、示例代码时读取对应 `reference/` 文件,避免一次性加载全部细节。
- **执行型**:按 `SKILL.md` 中的能力分组与调用契约,优先复用本地脚本(若存在),否则直接调用开放平台接口完成任务并返回结果。
## 许可证
本仓库由紫鸟开放平台维护,内容包含接口文档与执行型 Skill,供开发者集成对接与自动化使用。使用本仓库即表示你已阅读并同意紫鸟开放平台的相关服务条款;接口的实际调用受开放平台的鉴权、计费与限流策略约束。
@@ -0,0 +1,301 @@
---
name: linkfoxai-image-tool
description: 这是紫鸟开放平台Skill,面向普通用户直接执行任务,无需关心内部调用细节;当用户要求直接完成图片上传、场景裂变、商品替换、自动抠图等当前 Skill 覆盖能力,并希望直接拿到最终结果时使用本 Skill。执行时优先复用本地脚本(若存在),否则通过开放平台接口完成调用。
---
# linkfoxai-image-tool
## Trigger Conditions
- 当用户目标属于当前 Skill 覆盖的能力分组,且希望直接拿到最终结果时,可使用本 Skill。
- 当前 Skill 覆盖的能力分组:
- `linkfox-image-upload`: 图片上传
- `linkfox-scene-fission`: 场景裂变
- `linkfox-shop-replace`: 商品替换
- `linkfox-auto-image-matting`: 自动抠图
- 若用户目标是查询当前 Skill 覆盖任务的状态、处理进度或最终结果,也属于可直接执行的适用场景。
## When Not To Use
- 当用户只需要开放平台接入说明、参数解释、代码开发建议或集成设计时,不要使用本 Skill。
- 当请求不属于当前 Skill 覆盖的能力范围时,不要强行复用本 Skill。
- 当需要你猜测未在契约中声明的参数、字段或调用行为时,不要继续执行,应以现有契约为准。
## Execution Flow
- `Step 1`: 读取当前 skill 的 `scripts/` 目录,确认是否存在可用的本地脚本及 `脚本使用指导.md`。
- `Step 2`: 若存在脚本,先读取 `scripts/脚本使用指导.md`,确认脚本的 payload、成功/失败语义与退出码规则。
- `Step 3`: 根据当前执行环境属于 `Win`、`macOS` 还是 `Linux`,按 `tool分组名_平台` 规则组合目标脚本名;`Win` 对应 `.ps1`,`macOS` 与 `Linux` 对应 `.sh`。
- `Step 4`: 若脚本存在且执行成功,直接使用脚本标准输出作为最终结果。
- `Step 5`: 若脚本不存在,或脚本执行失败并返回非 `0` 退出码,则改用对应的执行型 tool 直接调用开放平台,按 `requestUrl`、`Raw Request Contract` 与 `Raw Response Contract` 完成调用。
## Execution Prerequisites
- `callDomain`: `https://sbappstoreapi.ziniao.com/openapi-router`
- `apiKey`: 先确认 `ZNOPEN_API_KEY` 已可用;默认从 `~/.znopen/config.json` 读取,缺失时先提示用户提供,提供后回填文件,完成配置。
- `Authorization`: 无需让用户手工拼接请求头,tool 在调用开放平台时会基于可用的 `apiKey` 自动注入该头。
- `requestUrl`: 统一由 `callDomain + sourceOpenPath` 组成,调用前应直接使用已生成结果,不建议手工改写。
## Capability Groups
### linkfox-image-upload
- 适用请求:图片上传
- 开放平台执行 tool:`postImageV2UploadByBase64`
### linkfox-scene-fission
- 适用请求:场景裂变
- 开放平台执行 tool:`postImageV2MakeSceneFission`、`postImageV2MakeInfo`
### linkfox-shop-replace
- 适用请求:商品替换
- 开放平台执行 tool:`postImageV2MakeShopReplace`、`postImageV2MakeInfo`
### linkfox-auto-image-matting
- 适用请求:自动抠图
- 开放平台执行 tool:`postImageV2MakeCutout`、`postImageV2MakeInfo`
## Call Limits
- tool 发起开放平台请求时,必须自动带上请求头:`Authorization: Bearer {ApiKey}`。
- `Bearer` 和 `ApiKey` 之间必须有一个空格,不能省略、不能连写、也不能替换为其他前缀。
- 请求方法必须与对应 tool 的 `sourceMethod` 保持一致,不允许自行切换调用方式。
- 请求参数必须严格遵循 `Raw Request Contract`,响应解析与错误处理必须严格遵循 `Raw Response Contract`。
- 当前置条件未满足、凭证缺失或开放平台返回错误时,不应继续盲目重试或猜测字段含义。
## Tool Group Definitions
### linkfox-image-upload
#### `postImageV2UploadByBase64`
- Capability Group: `linkfox-image-upload`
- Use This Tool When: 当用户当前目标属于“图片上传”场景时使用;通过开放平台接口完成“图片上传”。
- Required Inputs: fileName、base64
- Returns: 重点查看返回字段:msg、traceId、code、viewUrl
- Tool Goal: 图片上传
##### Definition
```yaml
tool: postImageV2UploadByBase64
sourceApiName: 图片上传
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/uploadByBase64
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/uploadByBase64
```
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"fileName","id":"bzxlv5tv","type":"string","required":1,"desc":"文件名(带扩展名)支持格式:jpeg、jpg、png、webp","value":"1.png"},{"isRoot":false,"name":"base64","id":"6bzgux9i","type":"string","required":1,"desc":"图片数据base64编码 文件大小:不超过20MB","value":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"UploadByBase64ReqVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"viewUrl","id":"ohy4rmpb","type":"string","required":0,"desc":"上传成功后的图片访问地址"}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«ApiImageUploadResVo»"}]
```
### linkfox-scene-fission
#### `postImageV2MakeSceneFission`
- Capability Group: `linkfox-scene-fission`
- Use This Tool When: 当用户当前目标属于“场景裂变”场景时使用;通过开放平台接口完成“场景裂变”。
- Required Inputs: imageUrl、segImageUrl、strength、prompt
- Returns: 重点查看返回字段:msg、traceId、code、id
- Tool Goal: 场景裂变
##### Definition
```yaml
tool: postImageV2MakeSceneFission
sourceApiName: 场景裂变
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/make/sceneFission
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/make/sceneFission
```
##### Input Rules
- Image Input Rule: 当前契约只看到 URL 类图片字段,请按契约中的 `https` 图片地址字段传参。
- 不要自行追加 `base64` 字段,除非它已经明确出现在 `Raw Request Contract` 中。
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"imageUrl","id":"bzxlv5tv","type":"string","required":1,"desc":"原图","value":"https://test-file-ai.linkfox.com/UPLOAD/MANAGE/CASE/SCENE_FISSION/1744309376725860352/image.jpg"},{"isRoot":false,"name":"segImageUrl","id":"6bzgux9i","type":"string","required":0,"desc":"保留区域图,不传会自动调用智能抠图"},{"id":"r8mecvca","name":"strength","type":"number","desc":"生成图与原图相似度 默认值:0.5 值越大相似度度越高 浮点数 小数点后只有一位 可选范围 [0.1,1]","isInit":true,"required":0},{"id":"grl5efmb","name":"prompt","type":"string","desc":"强化内容描述,不传会自动根据参数图提取 最长600字符,超出会截断","isInit":true,"required":0},{"id":"87njvdrs","name":"imageOutputWidth","type":"integer","desc":"输出尺寸 宽度 最长不能超过2048,最短不能低于32","isInit":true,"required":0},{"id":"ukpogp3d","name":"imageOutputHeight","type":"integer","desc":"输出尺寸 高度 最长不能超过2048,最短不能低于32","isInit":true,"required":0},{"id":"ob2j7568","name":"provider","type":"string","desc":"场景裂变模式 SCENE_FISSION_REALISTIC:写实(默认值) SCENE_FISSION_SIMPLE:简约 SCENE_FISSION_INTELLIGENT:智能","isInit":true,"value":"SCENE_FISSION_REALISTIC"},{"id":"g017pou2","name":"outputNum","type":"integer","desc":"输出张数 取值范围:[1,4] 默认值:1","isInit":true,"required":0}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"SceneFissionParamsVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"id","id":"ohy4rmpb","type":"integer","required":0,"desc":"作图任务ID"}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«OnlyIdVo»"}]
```
#### `postImageV2MakeInfo`
- Capability Group: `linkfox-scene-fission`
- Use This Tool When: 当用户当前目标属于“场景裂变”场景时使用;通过开放平台接口完成“获取AI作图结果”。
- Required Inputs: id
- Returns: 重点查看返回字段:msg、traceId、code、id
- Reuse Note: 该 tool 名称在多个能力分组中复用;当前块表示它在 `linkfox-scene-fission` 场景下的使用入口,按当前分组目标理解即可。
- Tool Goal: 获取AI作图结果
##### Definition
```yaml
tool: postImageV2MakeInfo
sourceApiName: 获取AI作图结果
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/make/info
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/make/info
```
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"id","id":"bzxlv5tv","type":"string","required":1,"desc":"作图任务ID","value":"111"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"OnlyIdVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"id","id":"ohy4rmpb","type":"integer","required":0,"desc":"作图任务ID"},{"isRoot":false,"name":"status","id":"gcq4vu8w","type":"integer","required":0,"desc":"状态 1.排队中 2.生成中 3.成功 4.失败"},{"isRoot":false,"name":"errorCode","id":"6r3j5kog","type":"string","required":0,"desc":"错误码"},{"isRoot":false,"name":"errorMsg","id":"46vvp4b4","type":"string","required":0,"desc":"错误文字描述"},{"isRoot":false,"name":"resultList","id":"k0lmm7mt","type":"array","required":0,"desc":"输出结果图片数组","children":[{"isRoot":false,"name":"id","id":"item1","type":"integer","required":0,"desc":"图片ID"},{"isRoot":false,"name":"status","id":"item2","type":"integer","required":0,"desc":"状态 0.生成中 1.成功 2.失败"},{"isRoot":false,"name":"url","id":"item3","type":"string","required":0,"desc":"高清图"},{"isRoot":false,"name":"width","id":"item4","type":"integer","required":0,"desc":"宽"},{"isRoot":false,"name":"height","id":"item5","type":"integer","required":0,"desc":"高"},{"isRoot":false,"name":"format","id":"item6","type":"string","required":0,"desc":"格式"}]}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«ApiImageMakeResVo»"}]
```
### linkfox-shop-replace
#### `postImageV2MakeShopReplace`
- Capability Group: `linkfox-shop-replace`
- Use This Tool When: 当用户当前目标属于“商品替换”场景时使用;通过开放平台接口完成“商品替换”。
- Required Inputs: imageUrl、sourceImageUrl、targetOriginUrl、targetImageUrl
- Returns: 重点查看返回字段:msg、traceId、code、id
- Tool Goal: 商品替换
##### Definition
```yaml
tool: postImageV2MakeShopReplace
sourceApiName: 商品替换
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/make/shopReplace
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/make/shopReplace
```
##### Input Rules
- Image Input Rule: 当前契约只看到 URL 类图片字段,请按契约中的 `https` 图片地址字段传参。
- 不要自行追加 `base64` 字段,除非它已经明确出现在 `Raw Request Contract` 中。
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"imageUrl","id":"bzxlv5tv","type":"string","required":1,"desc":"商品原图","value":"https://linkfoxai-ailab.oss-cn-shenzhen.aliyuncs.com/UPLOAD/1701144666432344064/2024/11/18/96a7a42fc965491dbc625d2db09f2478.png"},{"isRoot":false,"name":"sourceImageUrl","id":"6bzgux9i","type":"string","required":0,"desc":"原图抠出来的商品图"},{"id":"r8mecvca","name":"targetOriginUrl","type":"string","desc":"替换的目标原图","isInit":true,"required":1,"value":"https://linkfoxai-ailab.oss-cn-shenzhen.aliyuncs.com/UPLOAD/1701144666432344064/2024/11/18/9778678b4724467485346248032387ec.png"},{"id":"grl5efmb","name":"targetImageUrl","type":"string","desc":"替换的目标原图抠图结果","isInit":true,"required":0},{"id":"87njvdrs","name":"denoiseStrength","type":"string","desc":"生成图变化程度 默认值:0.5 越大越高 浮点数 可选范围 [0,1]","isInit":true,"required":0},{"id":"ukpogp3d","name":"imageOutputWidth","type":"integer","desc":"输出尺寸 宽度 最长边不能超过4096,最短边不能低于32","isInit":true,"required":0},{"id":"g017pou2","name":"imageOutputHeight","type":"integer","desc":"输出尺寸 高度 最长边不能超过4096,最短边不能低于32","isInit":true,"required":0},{"id":"vtarf9ph","name":"outputNum","type":"integer","desc":"输出张数 取值范围:[1,4] 默认值:1","isInit":true,"required":0}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ShopReplaceParamsVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"id","id":"ohy4rmpb","type":"integer","required":0,"desc":"作图任务ID"}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«OnlyIdVo»"}]
```
#### `postImageV2MakeInfo`
- Capability Group: `linkfox-shop-replace`
- Use This Tool When: 当用户当前目标属于“商品替换”场景时使用;通过开放平台接口完成“获取AI作图结果”。
- Required Inputs: id
- Returns: 重点查看返回字段:msg、traceId、code、id
- Reuse Note: 该 tool 名称在多个能力分组中复用;当前块表示它在 `linkfox-shop-replace` 场景下的使用入口,按当前分组目标理解即可。
- Tool Goal: 获取AI作图结果
##### Definition
```yaml
tool: postImageV2MakeInfo
sourceApiName: 获取AI作图结果
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/make/info
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/make/info
```
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"id","id":"bzxlv5tv","type":"string","required":1,"desc":"作图任务ID","value":"111"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"OnlyIdVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"id","id":"ohy4rmpb","type":"integer","required":0,"desc":"作图任务ID"},{"isRoot":false,"name":"status","id":"gcq4vu8w","type":"integer","required":0,"desc":"状态 1.排队中 2.生成中 3.成功 4.失败"},{"isRoot":false,"name":"errorCode","id":"6r3j5kog","type":"string","required":0,"desc":"错误码"},{"isRoot":false,"name":"errorMsg","id":"46vvp4b4","type":"string","required":0,"desc":"错误文字描述"},{"isRoot":false,"name":"resultList","id":"k0lmm7mt","type":"array","required":0,"desc":"输出结果图片数组","children":[{"isRoot":false,"name":"id","id":"item1","type":"integer","required":0,"desc":"图片ID"},{"isRoot":false,"name":"status","id":"item2","type":"integer","required":0,"desc":"状态 0.生成中 1.成功 2.失败"},{"isRoot":false,"name":"url","id":"item3","type":"string","required":0,"desc":"高清图"},{"isRoot":false,"name":"width","id":"item4","type":"integer","required":0,"desc":"宽"},{"isRoot":false,"name":"height","id":"item5","type":"integer","required":0,"desc":"高"},{"isRoot":false,"name":"format","id":"item6","type":"string","required":0,"desc":"格式"}]}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«ApiImageMakeResVo»"}]
```
### linkfox-auto-image-matting
#### `postImageV2MakeCutout`
- Capability Group: `linkfox-auto-image-matting`
- Use This Tool When: 当用户当前目标属于“自动抠图”场景时使用;通过开放平台接口完成“自动抠图”。
- Required Inputs: imageUrl、subType、clothClass
- Returns: 重点查看返回字段:msg、traceId、code、id
- Tool Goal: 自动抠图
##### Definition
```yaml
tool: postImageV2MakeCutout
sourceApiName: 自动抠图
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/make/cutout
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/make/cutout
```
##### Input Rules
- Image Input Rule: 当前契约只看到 URL 类图片字段,请按契约中的 `https` 图片地址字段传参。
- 不要自行追加 `base64` 字段,除非它已经明确出现在 `Raw Request Contract` 中。
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"imageUrl","id":"bzxlv5tv","type":"string","required":1,"desc":"原图","value":"https://linkfoxai-ailab.oss-cn-shenzhen.aliyuncs.com/UPLOAD/1701144666432344064/2024/11/13/bec32c4461644befb202f86b23502583.png"},{"isRoot":false,"name":"subType","id":"6bzgux9i","type":"integer","required":1,"desc":"抠图类型 1: 通用 2: 人像 3: 商品 9: 服饰 12: 头发 13:人脸","value":"1"},{"id":"r8mecvca","name":"clothClass","type":"string","desc":"服饰分类,只有抠图类型=9(服饰抠图)时才会生效, 可多选,以','号分割","isInit":true,"required":0}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"CutoutParamsVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"id","id":"ohy4rmpb","type":"integer","required":0,"desc":"作图任务ID"}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«OnlyIdVo»"}]
```
#### `postImageV2MakeInfo`
- Capability Group: `linkfox-auto-image-matting`
- Use This Tool When: 当用户当前目标属于“自动抠图”场景时使用;通过开放平台接口完成“获取AI作图结果”。
- Required Inputs: id
- Returns: 重点查看返回字段:msg、traceId、code、id
- Reuse Note: 该 tool 名称在多个能力分组中复用;当前块表示它在 `linkfox-auto-image-matting` 场景下的使用入口,按当前分组目标理解即可。
- Tool Goal: 获取AI作图结果
##### Definition
```yaml
tool: postImageV2MakeInfo
sourceApiName: 获取AI作图结果
sourceMethod: POST
sourceOpenPath: /linkfox-ai/image/v2/make/info
requestUrl: https://sbappstoreapi.ziniao.com/openapi-router/linkfox-ai/image/v2/make/info
```
##### Raw Request Contract
```text
{"headers":[{"isRoot":false,"name":"Content-Type","id":"w8pqf70e","type":"","value":"application/json","required":1,"desc":""}],"query":[],"body":[{"isRoot":true,"children":[{"isRoot":false,"name":"id","id":"bzxlv5tv","type":"string","required":1,"desc":"作图任务ID","value":"111"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"OnlyIdVo"}]}
```
##### Raw Response Contract
```text
[{"isRoot":true,"children":[{"isRoot":false,"name":"msg","id":"474ze4ft","type":"string","required":0,"desc":"结果描述"},{"isRoot":false,"name":"traceId","id":"gcq4vu8w","type":"string","required":0,"desc":"链路追踪id"},{"isRoot":false,"name":"code","id":"6r3j5kog","type":"integer","required":0,"desc":"结果编码 200成功 非200报错"},{"isRoot":false,"children":[{"isRoot":false,"name":"id","id":"ohy4rmpb","type":"integer","required":0,"desc":"作图任务ID"},{"isRoot":false,"name":"status","id":"gcq4vu8w","type":"integer","required":0,"desc":"状态 1.排队中 2.生成中 3.成功 4.失败"},{"isRoot":false,"name":"errorCode","id":"6r3j5kog","type":"string","required":0,"desc":"错误码"},{"isRoot":false,"name":"errorMsg","id":"46vvp4b4","type":"string","required":0,"desc":"错误文字描述"},{"isRoot":false,"name":"resultList","id":"k0lmm7mt","type":"array","required":0,"desc":"输出结果图片数组","children":[{"isRoot":false,"name":"id","id":"item1","type":"integer","required":0,"desc":"图片ID"},{"isRoot":false,"name":"status","id":"item2","type":"integer","required":0,"desc":"状态 0.生成中 1.成功 2.失败"},{"isRoot":false,"name":"url","id":"item3","type":"string","required":0,"desc":"高清图"},{"isRoot":false,"name":"width","id":"item4","type":"integer","required":0,"desc":"宽"},{"isRoot":false,"name":"height","id":"item5","type":"integer","required":0,"desc":"高"},{"isRoot":false,"name":"format","id":"item6","type":"string","required":0,"desc":"格式"}]}],"name":"data","id":"k0lmm7mt","type":"object","required":0},{"isRoot":false,"name":"msgKey","id":"46vvp4b4","type":"string","required":0,"desc":"错误编码"}],"name":"","id":0,"type":"object","value":"","required":0,"desc":"ResponseDto«ApiImageMakeResVo»"}]
```
@@ -0,0 +1,132 @@
---
name: ziniao-erp-api-doc
description: >
Use when 需要理解紫鸟ERP API 的定位、能力、权限体系或调用流程,
用于 ERP 系统对接规划、集成设计或接口评估。
不要用于直接查阅具体接口参数——查阅时读取 reference/ 下对应文件。
---
# ziniao-erp-api-doc(紫鸟ERP API 文档)
## 何时使用 / 不使用
| 场景 | 做法 |
|------|------|
| 需要了解紫鸟 API 的能力和模块 | 读本 Skill |
| 需要了解接口权限点以申请开通 | 读本 Skill §权限点汇总 |
| 需要做 ERP 对接的集成设计 | 读本 Skill |
| 需要查阅某个接口的详细参数 | 读本 Skill §按需深入,再读 reference/ 文件 |
---
## Level 0:定位与边界
### 是什么
紫鸟ERP REST API,面向 ERP/第三方系统,通过"简单通用模式"(Bearer API Key)认证,实现紫鸟浏览器的企业级管理——涵盖账号、设备、访问策略、角色权限、部门员工五大模块,共 72 个接口。
### 能力概览
| 能力域 | 接口数 | 说明 |
|--------|--------|------|
| 账号管理 | 22 | 账号 CRUD、授权管理、标签体系、缓存清除 |
| 设备管理 | 11 | 套餐/设备购买续费、绑定解绑、自有设备管理 |
| 访问策略 | 22 | 网页/分组管理、访问规则 CRUD、账号策略绑定 |
| 角色和权限 | 7 | 角色 CRUD、权限列表、用户角色调整 |
| 部门员工 | 10 | 部门 CRUD/移动、员工新增/修改/查询/启禁用 |
### 不做什么(边界)
- 不包含 OAuth 授权模式(仅覆盖简单通用模式)
- 不包含浏览器指纹配置或窗口管理
- 不包含账单/支付明细(仅含设备购买/续费操作)
- 不包含企业创建/注销等顶级管理
---
## Level 1:架构与流程
### 核心概念
**API Key 认证**:在开放平台创建"卖家自研应用"并选择"简单通用模式"获取 API Key。请求头:`Authorization: Bearer {API_Key}`。
**双层响应结构**:网关层(request_id / code / msg / data(业务层))+ 业务层(ret / status / msg + 业务字段)。网关 code="0" 到达业务层;业务 ret=0 操作成功。失败时额外返回 sub_code / sub_msg。注意:业务数据不一定在业务层的 data 字段中,部分接口的业务字段直接位于业务层根级(参见各 reference 文件中的响应说明)。
**核心 ID 体系**:companyId(紫鸟企业ID,几乎所有接口必传,可在开放平台首页右侧获取)、storeId(账号ID)、userId/staffId(用户ID)、proxyId(设备ID)、tagId/tid(标签ID)、roleId(角色ID)、ruleId(规则ID)。
**权限点体系**:每个接口归属一个"所属权限点",应用需在开放平台申请开通对应权限后才能调用。多个接口可共享同一权限点。
### 调用流程
```mermaid
graph LR
A[获取 API Key] --> B[申请权限点]
B --> C[构造请求头]
C --> D[POST 请求]
D --> E{网关 code=0?}
E -- 是 --> F{业务 ret=0?}
E -- 否 --> G[检查 Key/权限]
F -- 是 --> H[解析业务字段]
F -- 否 --> I[读取 msg]
```
Base URL:`https://sbappstoreapi.ziniao.com/openapi-router`
### 权限点汇总
应用需在开放平台申请开通以下权限点,才能调用对应接口:
| 模块 | 需申请的权限点 |
|------|-------------|
| 账号管理 | ERP-创建与删除账号权限、ERP-账号查看权限、ERP-编辑账号基础信息、ERP-账号授权权限、ERP-清除账号授权、ERP-清除账号缓存、ERP-查询某用户有权限的账号列表、ERP-标签列表、账号标签管理权限 |
| 设备管理 | ERP-设备套餐列表查询权限、ERP-设备购买与续费权限、ERP-设备绑定权限、ERP-设备查询、ERP-开关自动续费、ERP-查询已购设备价格接口、ERP-解绑设备、ERP-添加自有设备(新)、ERP-修改自有设备信息(新) |
| 访问策略 | ERP-网页访问权限(覆盖全部 22 个访问策略接口) |
| 角色权限 | ERP-角色添加、修改权限、ERP-角色列表查询、ERP-角色详情、ERP-权限列表 |
| 部门员工 | ERP-部门与员工接口、ERP-用户的部门变更 |
每个 reference 文件中标注了各接口的具体权限点归属。
### 公共响应结构
```json
{
"request_id": "全局请求追踪ID",
"code": "0",
"msg": "SUCCESS",
"data": {
"ret": 0,
"status": "success",
"data": [],
"msg": "",
"count": 0
}
}
```
> **注意**:业务数据位置因接口而异。多数接口的业务数据在 `data.data` 中,但部分接口的业务字段直接在业务层根级(与 ret/status 同级)。例如"ERP-员工新增v2"返回的 id、password 直接在业务层,不在 data 字段内。请以各 reference 文件中"响应 data"或"响应"的标注为准——标注"响应 data"表示数据在 data 字段中,标注"响应"则表示字段在业务层根级。
### 环境约定
- 请求编码:UTF-8,Content-Type:application/json
- 域名:`https://sbappstoreapi.ziniao.com/openapi-router`
- 几乎所有接口均为 POST + JSON body
- 前提:创建"卖家自研应用",选择"简单通用模式",申请所需权限点,配置 IP 白名单
---
## 按需深入(Level 2 路由表)
> **规则:以下内容不要主动加载。**
> 仅在需要了解某个细节时,按路由表读取对应的 reference/ 文件。
| 你需要了解 | 读取文件 |
|-----------|---------|
| 认证方式、请求构造、代码示例(Java/Python/JS)、常见错误 | `reference/api-guide.md` |
| 账号创建/删除/编辑/列表查询/标签列表/缓存清除 | `reference/account-crud.md` |
| 账号授权新增/删除/查询/清除/授权用户列表 | `reference/account-auth.md` |
| 标签添加/删除/重命名/绑定/解绑/替换/移除/清空/列表 | `reference/account-tags.md` |
| 设备套餐/购买/续费/绑定/解绑/查询/自有设备管理 | `reference/device-management.md` |
| 网页增删改查/网页分组管理 | `reference/access-policy-web.md` |
| 访问规则新建/编辑/删除/启禁用/详情/成员账号设置 | `reference/access-policy-rules.md` |
| 角色增改查/权限列表/用户角色调整 | `reference/roles-permissions.md` |
| 部门增删改查移动/员工增改查启禁用/部门变更 | `reference/department-staff.md` |
@@ -0,0 +1,219 @@
# 访问策略 - 访问规则与账号策略接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅访问规则的新建、编辑、删除、启禁用、详情,以及规则成员/账号设置、账号策略绑定接口时加载。
所有接口**权限点**均为 **ERP-网页访问权限**,方法均为 POST。
---
## ERP-访问规则新建
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/add`
> addIds 与 addNames 可任传其一,都传取并集。
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| name | 规则名称 | 是 | string |
| desc | 规则描述 | 是 | string |
| activeUser | 生效成员(1除boss外所有/2指定成员/3指定部门/4指定角色) | 是 | number |
| userChange | 指定成员对象 | 是 | object |
| userChange.addIds | 新增成员id列表(空值传[]) | 否 | string[] |
| userChange.addNames | 新增成员用户名列表 | 否 | string[] |
| userChange.departmentIds | 指定部门列表 | 否 | number[] |
| userChange.roleIds | 指定角色列表 | 否 | number[] |
| activeAccount | 生效账号(0所有/1指定) | 是 | number |
| accountChange | 指定账号对象 | 是 | object |
| accountChange.addIds | 账号id列表 | 否 | string[] |
| activeRange | 生效范围(1所有网站/2黑名单/3白名单) | 是 | string |
| configs | 配置详情数组 | 是 | array |
| configs[].urlId | 网页ID(activeRange=1时传0) | 是 | number |
| configs[].isAccessible | 是否可访问 | 是 | boolean |
| configs[].isLog | 是否记录日志 | 是 | boolean |
| configs[].functions | 操作控制配置数组 | 是 | array |
| configs[].functions[].function | clipboard/file_upload/file_download/print/console/password/mouse_click/keyboard_input | 是 | string |
| configs[].functions[].permission | 1允许/2允许且记录/3限制且记录/6限制 | 是 | number |
| isSkipRuleConflictCheck | 是否跳过冲突检测 | 是 | boolean |
| effectiveTime | 生效时间(可选) | 否 | object |
| effectiveTime.timeType | 0永久 1限制时间 | 是 | string |
| effectiveTime.startTime | 开始时间戳(秒),永久填0 | 是 | number |
| effectiveTime.endTime | 结束时间戳(秒),永久填0 | 是 | number |
| isAllowRequest | 是否允许申请访问(默认true) | 否 | boolean |
| shareCompanyIds | 合作公司id列表 | 否 | number[] |
| domIds | dom列表 | 否 | number[] |
**响应 data**:含 id(number)。
---
## ERP-访问规则编辑
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/edit`
> addIds/addNames 可任传其一取并集;deleteIds/deleteNames 不能与 add 重复。
参数结构与「规则新建」基本一致,差异:
- 新增 `ruleId`(number, 必须) — 规则ID
- `userChange` / `accountChange` 增加 `deleteIds`(string[]) 和 `deleteNames`(string[]) 用于删除成员
- 无 `activeRange`、`isSkipRuleConflictCheck` 参数
**响应**:ret / msg
---
## ERP-访问规则删除
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/delete`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ruleIds | 规则ID列表 | 是 | number[] |
**响应**:ret / msg
---
## ERP-访问规则启用禁用
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/active`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ruleIds | 规则ID列表 | 是 | number[] |
| isActive | 0禁用 1启用 | 是 | string |
**响应**:ret / msg
---
## ERP-访问规则列表
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/list`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| search | 搜索关键字 | 否 | string |
| activeRange | 1所有网站/2黑名单/3白名单 | 否 | number |
| isActive | 0禁用 1启用 | 否 | number |
| ruleListType | 0全部 1账号可绑定 | 否 | number |
| accountId | 账号id(获取可绑定规则时传) | 否 | number |
| page | 页码 | 是 | number |
| limit | 每页条数 | 是 | number |
**响应 data**:含 list 数组(每项含 id、name、activeUser、activeRange、isActive、description、users[]、roles[]、departments[]、accounts[]、presetType、canChangeUser/Account/Url/Active/canDelete/canCopy)和 count。
---
## ERP-访问规则详情
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/detail`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ruleId | 规则ID | 是 | number |
**响应 data**:含 id、name、description、activeUser、activeRange、activeAccount、users[{id,name,username}]、roles[{id,name}]、departments[{id,name}]、accounts[{id,name}]、configs[{urlId,urlName,groupId,groupName,tType,isAccessible,isLog,urlPattern,functions[{function,permission}]}]、effectiveTime{timeType,startTime,endTime}、shareCompanys[{id,name}]、domList[]、domIds[]。
---
## ERP-访问规则设置生效成员
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/change_user`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ruleId | 规则ID | 是 | number |
| activeUser | 1所有/2指定成员/3指定部门/4指定角色 | 是 | number |
| userChange.addIds | 新增成员id(空传[]) | 是 | string[] |
| userChange.addNames | 新增成员用户名 | 否 | string[] |
| userChange.deleteIds | 删除成员id(空传[]) | 是 | string[] |
| userChange.deleteNames | 删除成员用户名 | 否 | string[] |
| userChange.departmentIds | 指定部门 | 是 | number[] |
| userChange.roleIds | 指定角色 | 是 | number[] |
**响应**:ret / msg
---
## ERP-访问规则设置生效账号
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/change_account`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ruleId | 规则ID | 是 | number |
| activeAccount | 0所有 1指定 | 是 | number |
| accountChange.addIds | 新增账号id | 是 | string[] |
| accountChange.deleteIds | 删除账号id | 是 | string[] |
**响应**:ret / msg
---
## ERP-规则可绑定的账号列表
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/account/bindable_list`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| page | 页码 | 是 | number |
| limit | 每页条数 | 是 | number |
| ruleId | 规则id | 否 | number |
| platformIds | 平台id列表 | 否 | number[] |
| tagIds | 标签id列表 | 否 | number[] |
| accountName | 账号名称 | 否 | string |
**响应 data**:含 list(每项含 id、name、collocationRelation、sitePlatformName、storePlatformId、tags[{id,name}])、count(number)、bindAccountIds(number[])。
---
## ERP-账号添加访问策略
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/account_ref/add_to_account`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| accountId | 账号id | 是 | string |
| ruleIds | 访问策略id列表 | 是 | number[] |
| isSkipRuleConflictCheck | 是否跳过冲突检测 | 是 | boolean |
**响应**:ret / msg
---
## ERP-账号移除访问策略
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/account_ref/remove_from_account`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| accountId | 账号id | 是 | string |
| ruleIds | 访问策略id列表 | 是 | number[] |
**响应**:ret / msg
---
## ERP-账号绑定的访问策略列表
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule/list/one_account`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| accountId | 账号id | 是 | string |
| page | 页码 | 是 | number |
| limit | 每页条数 | 是 | number |
**响应 data**:含 list 数组(每项含 id、name、activeRange、activeUser、isActive、users[{id,name,username}]、urlCount、isInvalid、canDelete)和 count。
@@ -0,0 +1,152 @@
# 访问策略 - 网页与分组管理接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅网页增删改查、网页分组管理接口的详细参数时加载。
所有接口**权限点**均为 **ERP-网页访问权限**,方法均为 POST。
---
## ERP-添加网页
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url/add`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| name | 网页名称 | 是 | string |
| groupId | 分组ID | 是 | number |
| desc | 描述 | 否 | string |
| urlPattern | URL | 是 | string |
**响应 data**:含 id(number)。
---
## ERP-编辑网页
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url/edit`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| urlId | 网页ID | 是 | number |
| name | 网页名称 | 是 | string |
| groupId | 分组ID | 是 | number |
| desc | 描述 | 否 | string |
| urlPattern | URL | 是 | string |
**响应**:ret / msg
---
## ERP-删除网页
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url/delete`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| urlIds | 网页ID列表 | 是 | number[] |
**响应**:ret / msg
---
## ERP-网页列表
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url/list`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| search | 搜索关键字 | 否 | string |
| groupIds | 筛选分组ID | 否 | number[] |
| tType | 1系统预置 2自定义 | 否 | number |
| page | 页码 | 是 | number |
| limit | 每页条数 | 是 | number |
**响应 data**:含 list 数组(每项含 id、name、groupId、groupName、tType、urlPattern、description)和 count(number)。
---
## ERP-网页详情
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url/detail`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| urlId | 网页ID | 是 | number |
| urlPattern | 网页url(按url查详情) | 否 | string |
**响应 data**:含 id、name、groupId、groupName、tType、urlPattern、description。
---
## ERP-网页修改分组
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url/change_group`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| groupId | 目标分组ID | 是 | number |
| urlIds | 网页ID列表 | 是 | number[] |
**响应**:ret / msg
---
## ERP-添加网页分组
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url_group/add`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| name | 分组名称 | 是 | string |
| groupIdParent | 父级分组ID(0为顶级,默认0) | 否 | number |
**响应 data**:含 id(number)。
---
## ERP-编辑网页分组
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url_group/edit`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| groupId | 分组ID | 是 | number |
| name | 分组名称 | 是 | string |
**响应**:ret / msg
---
## ERP-删除网页分组
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url_group/delete`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| groupId | 分组ID | 是 | number |
**响应**:ret / msg
---
## ERP-网页分组列表
- **路径**:`/superbrowser/rest/v1/erp/security/access_rule_url_group/list`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| groupIdParent | 父级id(0为顶级,默认0) | 否 | number |
| isAll | 是否返回全部数据(默认false) | 否 | boolean |
**响应 data**:含 list 数组,每项含 id(number)、name(string)、tType(1系统/2自定义)、urlCount(number)、childrenCount(number)、groupIdParent(number)、children(array,isAll=true 时返回,结构同父节点)。
@@ -0,0 +1,76 @@
# 账号授权管理接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅账号授权新增、删除、查询、清除、授权用户列表接口的详细参数时加载。
---
## ERP-账号授权新增
- **路径**:`/superbrowser/rest/v1/erp/store/auth/add` **方法**:POST **权限点**:ERP-账号授权权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeIdList | 账号id列表 | 是 | string[] |
| staffId | 授权用户id | 是 | string |
| isAuthAddtion | 是否授权附加账号(默认授权)0否 1是 | 否 | number |
**响应**:ret / status / msg
---
## ERP-账号授权删除
- **路径**:`/superbrowser/rest/v1/erp/store/auth/delete` **方法**:POST **权限点**:ERP-账号授权权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeIdList | 账号id列表 | 是 | string[] |
| staffId | 授权用户id | 是 | string |
**响应**:ret / status / msg
---
## ERP-账号授权查询
- **路径**:`/superbrowser/rest/v1/erp/store/auth/query` **方法**:POST **权限点**:ERP-账号查看权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| userId | 用户id | 是 | number |
**响应 data**:数组,每项含 storeId(number)、storeName(string)。
---
## ERP-清除账号授权
- **路径**:`/superbrowser/rest/v1/erp/store/auth/clean` **方法**:POST **权限点**:ERP-清除账号授权
> 清除指定账号上的所有授权关系(不指定用户)。
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeIdList | 账号id数组 | 是 | string[] |
**响应**:ret / msg
---
## ERP-账号授权用户列表查询
- **路径**:`/superbrowser/rest/v1/erp/store/user/list` **方法**:POST **权限点**:ERP-账号查看权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeIdList | 账号id列表 | 否 | string[] |
| page | 页码 | 否 | string |
| limit | 每页条数 | 否 | string |
**响应 data**:数组,每项含 storeId(number)、userId(number)、username(string)。另有 count。
@@ -0,0 +1,134 @@
# 账号增删改查与状态接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅账号创建、删除、编辑、列表查询、标签列表、缓存清除接口的详细参数时加载。
---
## ERP-创建账号
- **路径**:`/superbrowser/rest/v1/erp/store/create` **方法**:POST **权限点**:ERP-创建与删除账号权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeData | 账号数据数组 | 是 | array |
| storeData[].name | 账号名 | 是 | string |
| storeData[].username | 平台登录账号 | 否 | string |
| storeData[].password | 平台登录密码 | 否 | string |
| storeData[].siteId | 站点id | 否 | string |
| storeData[].tagIds | 标签列表(不超过5个) | 否 | number[] |
| proxyId | 设备id | 否 | number |
| enterpriseName | 企业简称 | 否 | string |
| defyWarning | 是否无视风险添加:1是 0否(默认1) | 否 | string |
**响应 data**:数组,每项含 storeId(string)、name(string)、siteId(string)、msg(string)、riskMsgList(string[])
---
## ERP-删除账号
- **路径**:`/superbrowser/rest/v1/erp/store/delete` **方法**:POST **权限点**:ERP-创建与删除账号权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeIds | 账号id数组 | 是 | number[] |
**响应**:ret / status / msg
---
## ERP-编辑账号基础信息
- **路径**:`/superbrowser/rest/v1/erp/store/update/base` **方法**:POST **权限点**:ERP-编辑账号基础信息
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| id | 账号id | 是 | number |
| name | 账号名(≤64位,不传不更新) | 否 | string |
| siteId | 平台id(不传不更新) | 否 | number |
| tagIds | 标签id列表(不传不更新,传空清空,≤5个) | 否 | number[] |
| defyWarning | 是否无视风险:1是 0否(默认1) | 否 | integer |
| customerUrl | 自定义平台站点url | 否 | string |
**响应**:ret / msg / status
---
## ERP-账号列表查询
- **路径**:`/superbrowser/rest/v1/erp/store/list` **方法**:POST **权限点**:ERP-账号查看权限
> 私有化账号建议一次最多查询 20 条。
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeName | 账号名称 | 否 | string |
| ip | 设备ip | 否 | string |
| page | 页码 | 否 | number |
| limit | 每页条数 | 否 | number |
**响应 data**:数组,每项含 id、name、ip、proxyId、siteId、siteName、platform、platformName、username、createtime、lastuserId、lastusetime、packageName (均 string)。另有 count。
---
## ERP-用户的账号列表查询
- **路径**:`/superbrowser/rest/v1/erp/store/list/by/user` **方法**:POST **权限点**:ERP-账号查看权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| userId | 用户id | 是 | number |
| storeName | 账号名称 | 否 | string |
| page | 页码 | 否 | number |
| limit | 每页条数 | 否 | number |
**响应 data**:数组,每项含 storeId、storeName、proxyId、ipExpiryTime、platformName (均 string)。另有 count。
---
## ERP-查询某用户有权限的账号列表
- **路径**:`/superbrowser/rest/v1/erp/user/stores` **方法**:POST **权限点**:ERP-查询某用户有权限的账号列表
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| userId | 用户id | 是 | number |
| storeName | 账号名称 | 否 | string |
| isAccurate | 0模糊 1精确(默认精确) | 否 | number |
| page | 页码 | 否 | number |
| limit | 每页条数 | 否 | number |
**响应 data**:数组,每项含 id(number)、name、ipId(number)、ip、platform、platformCategory、userName、lastUserId、lastUserName、lastOpenUsername、lastUsetime、ipExpiryTime、tags(string[])、regionInfo。另有 count。
---
## ERP-标签列表
- **路径**:`/superbrowser/rest/v1/erp/tag/list` **方法**:POST **权限点**:ERP-标签列表
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
**响应 data**:对象,含 list 数组,每项含 id(string)、name(string)。
---
## ERP-清除账号缓存
- **路径**:`/superbrowser/rest/v1/erp/store/deletecookie` **方法**:POST **权限点**:ERP-清除账号缓存
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| userId | 用户id | 是 | string |
| removeStoreId | 账号id | 是 | number |
| removeType | 1清除所有 2仅保留cookie 3仅保留二步验证状态 | 是 | number |
**响应**:ret / msg / status
@@ -0,0 +1,119 @@
# 账号标签管理接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅标签的添加、删除、重命名、绑定、解绑、替换、移除、清空、列表接口时加载。
所有标签接口**权限点**均为 **账号标签管理权限**。标签操作路径统一为 `/superbrowser/rest/v1/store-tag/{action}`,方法均为 POST。每个账号最多绑定 5 个标签。
**操作模式**:绑定/解绑为增量操作;替换为全量覆盖;清空移除所有标签。
---
## 添加账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/add`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 否 | string |
| userId | 用户id | 否 | string |
| tagName | 标签名称 | 否 | string |
**响应 data**:对象,含 tag_id(string)。
---
## 删除账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/delete`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 否 | string |
| tagIds | 标签id列表 | 否 | string[] |
| userId | 用户id | 否 | string |
**响应**:ret / msg
---
## 重命名账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/rename`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 否 | string |
| tid | 标签id | 否 | string |
| userId | 用户id | 否 | string |
| name | 新标签名称 | 否 | string |
**响应**:ret / status / msg
---
## 绑定账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/bind`(增量追加)
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 否 | string |
| storeIds | 账号id列表 | 否 | string[] |
| tagIds | 标签id列表 | 否 | string[] |
| userId | 用户id | 否 | string |
**响应**:ret / msg
---
## 解绑账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/unbind`(增量移除)
请求参数同「绑定账号标签」。**响应**:ret / status / msg
---
## 替换账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/replace`(全量覆盖)
请求参数同「绑定账号标签」。**响应**:ret / msg / status
---
## 移除账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/remove`
请求参数同「绑定账号标签」。**响应**:ret / msg / status
---
## 清空账号标签
- **路径**:`/superbrowser/rest/v1/store-tag/clear`(移除账号所有标签)
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 否 | string |
| storeIds | 账号id列表 | 否 | string[] |
| userId | 用户id | 否 | string |
**响应**:ret / msg
---
## 账号的标签列表
- **路径**:`/superbrowser/rest/v1/store-tag/list`
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | string |
| storeId | 店铺id | 是 | string |
| userId | 用户id | 是 | string |
| withSystem | 1带系统标签 0不带(默认0) | 否 | string |
**响应 data**:含 id(string)、name(string)、is_system(string)、store_count(string)。
@@ -0,0 +1,108 @@
# API 调用指南
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要了解认证方式、请求构造方法、代码示例或常见错误时加载。
## 概述
紫鸟开放平台使用"简单通用模式"进行 API 认证,通过 API Key 作为 Bearer Token 传递。
## 1. 获取 companyId(紫鸟企业ID)
在开放平台首页右侧可获取紫鸟企业ID(即 companyId),几乎所有接口都需要传此参数。
## 2. 获取 API Key
前提:在紫鸟开放平台创建"卖家自研应用",API 身份验证方式选择**简单通用模式**。在控制台中可以找到 API Key。
## 3. 配置 IP 白名单
在紫鸟开放平台的**应用管理**中,点击【设置】进入该应用的【应用概览】页面,点击【安全中心】下的【IP 白名单设置】,添加调用接口的服务器 IP。建议使用一台 IP 固定的服务器发起 API 请求。
## 4. 构造请求头
```
Authorization: Bearer {你的API_Key}
Content-Type: application/json
```
`Bearer` 和 API Key 之间必须有一个空格。
## 5. 请求地址
Base URL:`https://sbappstoreapi.ziniao.com/openapi-router`
完整请求地址 = Base URL + 接口路径。例如:
`https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/store/list`
## 6. 响应结构
### 网关层(外层)
| 字段 | 类型 | 说明 |
|------|------|------|
| request_id | string | 全局请求ID |
| code | string | "0" 表示成功 |
| msg | string | 返回码描述 |
| sub_code | string | 业务返回码(成功时不返回) |
| sub_msg | string | 业务返回码描述(成功时不返回) |
| data | object | 业务响应参数 |
### 业务层(data 内部)
| 字段 | 类型 | 说明 |
|------|------|------|
| ret | number | 0 成功,其他失败 |
| status | string | "success" 或 "faile" |
| data | array/object | 业务数据(部分接口有) |
| msg | string | 错误信息 |
| count | number | 列表查询时的总条数 |
| 其他字段 | - | 部分接口的业务字段直接在此层级 |
> **关键提醒**:业务数据位置因接口而异,不要假设所有业务数据都在 `data` 字段中。各 reference 文件对每个接口标注了"响应 data"或"响应"——"响应 data"表示数据在业务层的 data 字段中;"响应"后直接列出的字段(如 id、password)表示它们与 ret/status 同级,直接位于业务层根级。
## 7. 代码示例
### Java(OkHttp)
```java
OkHttpClient client = new OkHttpClient().newBuilder().build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\"companyId\": 123456}");
Request request = new Request.Builder()
.url("https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/store/list")
.method("POST", body)
.addHeader("Authorization", "Bearer 你的API_Key")
.addHeader("Content-Type", "application/json")
.build();
Response response = client.newCall(request).execute();
```
### Python(requests)
```python
import requests, json
url = "https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/store/list"
headers = {"Authorization": "Bearer 你的API_Key", "Content-Type": "application/json"}
response = requests.post(url, headers=headers, data=json.dumps({"companyId": 123456}))
print(response.json())
```
### JavaScript(Fetch)
```javascript
fetch("https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/store/list", {
method: "POST",
headers: { "Authorization": "Bearer 你的API_Key", "Content-Type": "application/json" },
body: JSON.stringify({ companyId: 123456 }),
})
.then(r => r.json()).then(console.log).catch(console.error);
```
## 8. 常见问题
**401 错误**:检查 API Key 是否正确、Authorization 格式是否含空格、接口是否支持通用模式、应用是否已申请对应权限点。
**code 非 "0"**:请求未到达业务层,通常是认证或权限问题。
**ret 非 0**:业务操作失败,读取 msg 或 sub_msg 获取错误详情。
@@ -0,0 +1,172 @@
# 部门员工管理接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅部门增删改查移动、员工新增/修改/查询/启禁用、部门变更接口时加载。
所有接口方法均为 POST。
---
## ERP-部门查询
- **路径**:`/superbrowser/rest/v1/erp/department/list` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
**响应 data**:数组,每项含 id(number)、name(string)、hierarchy(number 层级)、parentId(number)、ordering(number)。另有 count。
---
## ERP-部门新增
- **路径**:`/superbrowser/rest/v1/erp/department/add` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| name | 部门名称 | 是 | string |
| hierarchy | 部门级别 | 是 | string |
| parentId | 父级部门id | 是 | string |
**响应 data**:含 id(string 部门id)。
---
## ERP-部门修改
- **路径**:`/superbrowser/rest/v1/erp/department/update` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| id | 部门id | 是 | number |
| name | 部门新名称 | 否 | string |
| parentId | 父部门id | 否 | number |
**响应**:ret / status / msg
---
## ERP-部门删除
- **路径**:`/superbrowser/rest/v1/erp/department/delete` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | string |
| id | 部门id | 是 | string |
**响应**:ret / status / msg
---
## ERP-部门移动
- **路径**:`/superbrowser/rest/v1/erp/department/order` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| parentId | 父部门id | 是 | string |
| order | 更新后的部门顺序(部门id数组) | 是 | string[] |
**响应**:ret / status / msg
---
## ERP-员工查询
- **路径**:`/superbrowser/rest/v1/erp/staff/list` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| page | 页码 | 是 | number |
| limit | 每页条数 | 是 | number |
| username | 用户账号 | 否 | string |
| name | 真实姓名 | 否 | string |
| departmentIds | 部门id数组 | 否 | number[] |
| level | 角色类型(0boss/1经理/2员工/3超管) | 否 | string |
| delflag | 0正常 1禁用 2删除(不传全部) | 否 | string |
| isAccurate | 0模糊 1精确(默认0) | 否 | number |
**响应 data**:含 username、userId、name、level、mobile、delflag、authPhone (均 string)。另有 count。
---
## ERP-员工新增v2
- **路径**:`/superbrowser/rest/v2/erp/staff/add` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | string |
| nickname | 用户账号 | 是 | string |
| name | 真实姓名 | 是 | string |
| password | 密码 | 是 | string |
| mobile | 手机号 | 否 | string |
| departmentIds | 部门id数组 | 是 | string[] |
| roleId | 角色id | 是 | number |
| authDevide | 设备授权方式(1/2/3/4) | 是 | number |
| isUpdatePersonInfo | 1允许修改个人信息 0不允许 | 是 | number |
| isLimitLogin | 0否 1是 | 是 | number |
| isTwoStepVerify | 0否 1是 | 是 | number |
| authClient | 登录终端控制对象 | 是 | object |
| authClient.allowLoginWindows | 1允许 0不允许 | 是 | string |
| authClient.allowLoginMac | 1允许 0不允许 | 是 | string |
| authClient.allowLoginAndroid | 1允许 0不允许 | 是 | string |
| authClient.allowLoginWeb | 1允许 0不允许 | 是 | string |
| authClient.allowLoginIos | 1允许 0不允许 | 是 | string |
| authClient.allowLoginMiniprog | 1允许 0不允许 | 是 | string |
| authClient.allowLoginLinux | 1允许 0不允许 | 否 | string |
| loginLimitStartTime | HH:mm(分钟仅00或30) | 否 | string |
| loginLimitEndTime | HH:mm | 否 | string |
| loginLimitStartDate | yyyy-MM-dd | 否 | string |
| loginLimitEndDate | yyyy-MM-dd | 否 | string |
| enablePhoneLogin | 0否 1是 | 否 | number |
**响应**:含 id(用户id)、password(string)。
---
## ERP-员工修改v2
- **路径**:`/superbrowser/rest/v2/erp/staff/modify` **权限点**:ERP-部门与员工接口
参数结构与「员工新增v2」基本一致,差异:
- 新增 `id`(string, 必须) — 用户id
- 多数字段变为非必须(不传不更新)
- 新增 `isResetAuthPhone`(number) — 1重置登录手机号 0不重置
- `areaCode` 和 `authPhone` 已废弃
**响应**:ret / status / msg
---
## ERP-员工启用禁用接口
- **路径**:`/superbrowser/rest/v1/erp/staff/status` **权限点**:ERP-部门与员工接口
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| staffIds | 用户id数组 | 是 | string[] |
| status | 0启用 1禁用 2删除 | 是 | string |
**响应**:ret / status / msg
---
## ERP-用户的部门变更
- **路径**:`/superbrowser/rest/v1/erp/staff/department` **权限点**:ERP-用户的部门变更
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | string |
| staffIds | 用户id列表 | 是 | number[] |
| departmentIds | 部门id列表 | 是 | number[] |
**响应**:ret / msg / status
@@ -0,0 +1,180 @@
# 设备管理接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅设备套餐、购买、续费、绑定、解绑、查询、自有设备管理接口时加载。
所有接口方法均为 POST,Content-Type 为 application/json。
---
## ERP-套餐列表查询
- **路径**:`/superbrowser/rest/v1/erp/package/new` **权限点**:ERP-设备套餐列表查询权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
**响应 data**:数组,每项含 regionCloudId(套餐ID)、ctype(0站群/1云平台/2自有/3本地)、networkType、areaId、areaName、city、priceOriginal、deviceConfigName、deviceType(1标准/2节能/3高配)、deviceOs、deviceSupportRemoteLogin、deviceCpuSize、deviceMemorySize、deviceBandWidth、deviceDiskSize、platformName (均 string)。
---
## ERP-购买设备
- **路径**:`/superbrowser/rest/v1/erp/purchase` **权限点**:ERP-设备购买与续费权限
> 需要紫鸟账号上有余额。
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | string |
| purchase | 购买信息对象 | 是 | object |
| purchase.packageId | 套餐id | 是 | number |
| purchase.periodId | 时长id(1:1月/2:3月/3:6月/4:12月) | 是 | number |
| purchase.num | 数量(单次最多5个) | 是 | number |
| storeIds | 要绑定的账号id(未绑设备的) | 否 | number[] |
**响应 data**:含 order_id(string)、create_time(string)、proxyIdList(number[] 设备ID列表)。
---
## ERP-续费设备
- **路径**:`/superbrowser/rest/v1/erp/renew` **权限点**:ERP-设备购买与续费权限
> 仅支持紫鸟 IP 续费;自有设备请通过开启自动续费。
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | string |
| renew | 续费信息对象 | 是 | object |
| renew.periodId | 时长id | 是 | string |
| renew.ipIds | proxy_id 数组 | 是 | integer[] |
**响应 data**:含 id(设备id)、ip(设备ip)、expire(过期时间)。另有 orderId、createTime。
---
## ERP-开关自动续费
- **路径**:`/superbrowser/rest/v1/erp/ip/renewal` **权限点**:ERP-开关自动续费
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ids | proxy_id 数组 | 是 | number[] |
| isOpen | 0关闭 1开启 | 是 | number |
**响应**:ret / msg / status
---
## ERP-设备查询
- **路径**:`/superbrowser/rest/v1/erp/ip/page` **权限点**:ERP-设备查询
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| ipAddr | 设备地址 | 否 | string |
| page | 页码 | 否 | string |
| limit | 每页条数 | 否 | string |
**响应 data**:数组,每项含 id(number)、ip、proxyName、proxyType(0站群/1云平台/2自有/3vps/4虚拟机/5本地)、port、ptype(0http/1https/2socks5)、expiry、isRenewal(0关/1开)、createtime、delflag(0正常/2回收站) (均 string)。另有 count。
---
## ERP-绑定设备
- **路径**:`/superbrowser/rest/v1/erp/ip/bind` **权限点**:ERP-设备绑定权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| proxyId | 设备id | 是 | number |
| storeIdList | 账号id列表 | 是 | number[] |
| defyWarning | 是否无视风险:1是 0否(默认1) | 否 | number |
**响应**:ret / status / msg / riskMsgList(string[])
---
## ERP-解绑设备
- **路径**:`/superbrowser/rest/v1/erp/ip/unbind` **权限点**:ERP-解绑设备
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| storeId | 账号ID | 是 | number |
**响应**:ret / msg / status
---
## ERP-查询已购设备价格接口
- **路径**:`/superbrowser/rest/v1/erp/proxy/purchased_package_info` **权限点**:ERP-查询已购设备价格接口
> 仅支持查询云平台设备。
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司ID | 是 | number |
| proxyId | 设备ID | 是 | number |
**响应 data**:含 proxyId(number)、regionCloudId(number 套餐ID)、price(number 续费价格)。
---
## ERP-设备历史绑定记录
- **路径**:`/superbrowser/rest/v1/erp/ip/historybind` **权限点**:ERP-设备查询
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| proxyId | 设备id | 是 | number |
**响应 data**:对象含 list 数组,每项含 storeId、storeName、proxyName、unBindTime、unbindUserId、unbindUsername、cloudType (均 string)。另有 count。
---
## ERP-添加自有设备(新)
- **路径**:`/superbrowser/rest/v1/erp/ip/self/add/new` **权限点**:ERP-添加自有设备(新)
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司ID | 是 | string |
| defyWarning | 1无视风险 0风险提示 | 是 | number |
| base | 基础信息对象 | 是 | object |
| base.deviceName | 设备名称 | 是 | string |
| base.isDynamic | 是否动态网络(false静态/true动态) | 是 | boolean |
| proxy | 代理信息对象 | 是 | object |
| proxy.type | 0http/1https/2sock5/3ssh/4ssl | 是 | string |
| proxy.addr | 代理地址 | 是 | string |
| proxy.port | 代理端口 | 是 | string |
| proxy.username | 用户名 | 否 | string |
| proxy.passwd | 密码 | 否 | string |
| proxy.isSetAccount | 是否设密码 0否 1是 | 是 | number |
**响应 data**:含 id(number 设备ID)。
---
## ERP-修改自有设备信息(新)
- **路径**:`/superbrowser/rest/v1/erp/ip/self/edit/new` **权限点**:ERP-修改自有设备信息(新)
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司ID | 是 | string |
| defyWarning | 1无视风险 0风险提示 | 是 | number |
| proxyId | 设备ID | 是 | string |
| base | 基础信息对象 | 是 | object |
| base.deviceName | 设备名称(不传不更新) | 否 | string |
| base.isDynamic | 是否动态网络 | 是 | boolean |
| proxy | 代理信息对象(字段同添加接口) | 是 | object |
**响应**:ret / status / msg
@@ -0,0 +1,107 @@
# 角色和权限管理接口
> 本文件是 ziniao-erp-api-doc 的 Level 2 参考文档。
> 仅在需要查阅角色增改查、权限列表、用户角色调整接口的详细参数时加载。
所有接口方法均为 POST。
---
## ERP-添加角色
- **路径**:`/superbrowser/rest/v1/erp/per/role/add` **权限点**:ERP-角色添加、修改权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| roleName | 角色名称 | 是 | string |
| identityId | 角色身份id(1管理员/2普通成员/3超级管理员) | 是 | number |
| desc | 描述 | 否 | string |
| permissionIds | 权限id列表 | 是 | number[] |
**响应**:ret / status / msg
---
## ERP-修改角色
- **路径**:`/superbrowser/rest/v1/erp/per/role/edit` **权限点**:ERP-角色添加、修改权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| roleId | 角色id | 是 | string |
| roleName | 角色名称 | 是 | string |
| identityId | 角色身份id | 是 | number |
| desc | 描述 | 否 | string |
| permissionIds | 权限id列表 | 是 | number[] |
**响应**:ret / status / msg
---
## ERP-角色列表查询
- **路径**:`/superbrowser/rest/v1/erp/per/role/list` **权限点**:ERP-角色列表查询
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| page | 页码 | 否 | number |
| limit | 每页条数 | 否 | number |
| filterKeyword | 搜索关键字 | 否 | string |
**响应 data**:含角色信息 id、name、desc、identityType(1管理/2员工/3超管)、allPermission、users[{userId,name,username}]、permissions[{id,name,groupId,groupName,dependOn[],requiredBy[]}]。另有 count。
---
## ERP-角色详情
- **路径**:`/superbrowser/rest/v1/erp/per/role/detail` **权限点**:ERP-角色详情
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| roleId | 角色id | 是 | number |
**响应 data**:含 id、companyId、name、desc、permissionList[{id,name,groupId,groupName}] (均 string)。
---
## ERP-权限列表
- **路径**:`/superbrowser/rest/v1/erp/per/permission/list` **权限点**:ERP-权限列表
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| identityId | 角色身份id(1管理员/2普通成员/3超级管理员) | 是 | number |
**响应 data**:数组,每项含 id(number)、name(string)、desc(string)、order(number)、groupId(number)、groupName(string)、groupOrder(number)、dependOn(number[])、requiredBy(number[])。
---
## ERP-调整角色
- **路径**:`/superbrowser/rest/v1/erp/staff/change/role` **权限点**:ERP-角色添加、修改权限
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| roleId | 角色id | 是 | number |
| staffIds | 用户id列表 | 是 | number[] |
**响应**:ret / status / msg
---
## ERP-用户角色列表
- **路径**:`/superbrowser/rest/v1/erp/staff/role/list` **权限点**:ERP-角色列表查询
| 参数 | 说明 | 必须 | 类型 |
|------|------|------|------|
| companyId | 公司id | 是 | number |
| staffId | 用户id | 是 | number |
**响应 data**:对象含 roleList 数组,每项含 id(string)、name(string)。
@@ -0,0 +1,127 @@
---
name: ziniao-sso-doc
description: >
Use when 需要理解紫鸟单点登录 OpenAPI 的定位、能力、集成流程或接口清单,
用于 ERP 系统 SSO 对接规划、集成设计或接口评估。
不要用于直接查阅具体接口参数——查阅时读取 reference/ 下对应文件。
---
# ziniao-sso-doc(紫鸟单点登录 API 文档)
## 何时使用 / 不使用
| 场景 | 做法 |
|------|------|
| 需要了解紫鸟单点登录能做什么 | 读本 Skill |
| 需要做 ERP SSO 对接的集成设计 | 读本 Skill |
| 需要查阅某个接口的请求/响应参数 | 读本 Skill §按需深入 获取路径,再读 reference/ 文件 |
| 需要 Schema URL 协议详细参数 | 读 `reference/client-schema.md` |
---
## Level 0:定位与边界
### 是什么
紫鸟单点登录 OpenAPI,面向 ERP/第三方系统,通过服务端 API 获取认证令牌,结合 `superbrowser://` Schema URL 协议拉起紫鸟浏览器并自动登录指定账号,实现免密单点登录。支持两种鉴权模式:简单鉴权(直接用 API Key,无需调用 get_app_token)和复杂鉴权(需额外获取 appAuthToken)。
### 能力概览
| 能力域 | 说明 |
|--------|------|
| 应用认证 | 获取应用级 Token(appAuthToken)——仅复杂鉴权模式需要,简单鉴权模式直接用 API Key |
| 企业信息 | 获取自建应用关联的公司 ID(companyId) |
| 员工查询 | 分页查询公司员工列表,支持按角色/状态/部门筛选 |
| 账号查询 | 查询公司全部账号或指定用户有权限的账号列表 |
| 用户登录 | 获取指定用户的登录 Token(openapiToken),用于 SSO 鉴权 |
| 客户端控制 | 通过 Schema URL 打开/关闭账号、退出浏览器(仅 Windows) |
### 不做什么(边界)
- 不包含账号 CRUD(创建/删除/编辑)——见 ziniao-erp-api-doc
- 不包含标签、授权、设备管理接口——见 ziniao-erp-api-doc
- 不提供浏览器自动化(RPA/脚本注入)能力
- Schema URL 仅支持 Windows 客户端
---
## Level 1:架构与流程
### 核心概念
**两种鉴权模式**:**简单鉴权模式**——直接用 API Key 放入 `Authorization: Bearer {API_Key}` 请求头,无需调用 `get_app_token`;**复杂鉴权模式**——需先调用 `get_app_token` 获取 appAuthToken 再进行后续调用。大多数场景使用简单鉴权即可。
**用户登录 Token**:openapiToken,通过 `user-login` 接口获取,有过期时长,用于 Schema URL 拉起浏览器时的用户身份鉴权。
**Schema URL 协议**:以 `superbrowser://` 为前缀的自定义 URL 协议,支持三个 Action:OpenStrore(打开账号)、CloseStrore(关闭账号)、exit(退出浏览器)。可通过浏览器链接或命令行 `SuperBrowser.exe "url"` 调用。
**双层响应结构**:网关层(request_id / code / msg)+ 业务层(ret / status / data / msg)。网关 code="0" 表示请求到达业务层;业务 ret=0 表示操作成功。
### SSO 集成全流程
```mermaid
graph LR
A[创建应用<br/>获取 API Key] --> B[获取 companyId]
B --> C[查询员工列表]
C --> D[查询用户有权限的账号]
D --> E[获取用户 openapiToken]
E --> F[Schema URL<br/>打开账号]
```
1. 在开放平台创建自建应用,获取 API Key(简单鉴权模式)
2. 调用 `builtin/company` 获取 companyId(固定值,获取一次即可)
3. 调用 `staff/list` 查询员工,获取 userId
4. 调用 `user/stores` 查询该用户有权限的账号,获取 storeId
5. 调用 `user-login` 获取用户的 openapiToken(需提前申请接口权限)
6. 拼接 Schema URL:`superbrowser://OpenStrore?storeId={storeId}&openapiToken={token}&userId={userId}`
> 如使用复杂鉴权模式,需在步骤 1 之后额外调用 `get_app_token` 获取 appAuthToken。
### 接口摘要表
| 分组 | 接口名称 | 路径 | 方法 |
|------|---------|------|------|
| 认证 | 获取应用 token(仅复杂鉴权模式) | /auth/get_app_token | POST |
| 企业 | 获取公司信息 | /app/builtin/company | GET |
| 员工 | ERP-员工查询 | /superbrowser/rest/v1/erp/staff/list | POST |
| 账号 | ERP-账号列表查询 | /superbrowser/rest/v1/erp/store/list | POST |
| 账号 | ERP-查询用户有权限的账号 | /superbrowser/rest/v1/erp/user/stores | POST |
| 登录 | 获取用户登录 token | /superbrowser/rest/v1/token/user-login | POST |
### 公共响应结构
> **所有接口返回双层嵌套结构,解析时必须先剥离网关层再读业务层。**
> **异常处理:请求异常时,必须将 request_id、msg 和 sub_msg 都打印出来,便于追踪定位。**
```json
{
"request_id": "全局请求追踪ID",
"code": "0", // ← 网关层:code="0" 表示请求到达业务层
"msg": "SUCCESS",
"sub_code": "", // ← 业务返回码,成功时不返回
"sub_msg": "", // ← 业务返回码描述,成功时不返回
"data": { ... } // ← 业务层:各接口文档中的响应参数均在此内部
}
```
### 环境约定
- Base URL:`https://sbappstoreapi.ziniao.com/openapi-router`
- 请求编码:UTF-8,Content-Type:application/json
- 前提:在开放平台创建"卖家自研应用",获取 API Key,申请所需权限点,配置 IP 白名单
- `user-login` 接口需联系紫鸟客服申请权限(需 Boss 验证)
---
## 按需深入(Level 2 路由表)
> **规则:以下内容不要主动加载。**
> 仅在你明确需要了解某个细节时,按路由表读取对应的 reference/ 文件。
| 你需要了解 | 读取文件 |
|-----------|---------|
| 认证接口详细参数(get_app_token + user-login) | `reference/account-auth.md` |
| 企业信息、员工查询、账号查询接口的详细参数 | `reference/account-crud.md` |
| Schema URL 协议详细参数(OpenStrore / CloseStrore / exit) | `reference/client-schema.md` |
| API 使用指南(基地址、公共 Headers、公共响应、curl 示例) | `reference/api-guide.md` |
@@ -0,0 +1,129 @@
# 认证鉴权接口
> 本文件是 ziniao-sso-doc 的 Level 2 参考文档。
> 仅在需要查阅 get_app_token 或 user-login 接口的详细参数时加载。
>
> **响应说明**:以下各接口的"响应参数"均为业务层字段,实际位于网关响应的 `data` 内部。完整响应结构见 `api-guide.md` 公共响应参数。
## 获取应用 Token
> 仅在"复杂鉴权模式"下需要调用此接口。如使用"简单鉴权模式"(直接用 API Key),无需调用此接口,跳过本节即可。
### 基本信息
| 项目 | 值 |
|------|------|
| 路径 | `/auth/get_app_token` |
| 方法 | POST |
| Content-Type | application/json |
### 请求 Headers
| 参数名称 | 参数值 | 是否必须 |
|----------|--------|----------|
| Content-Type | application/json | true |
| Authorization | Bearer {API_Key} | true |
### 请求 Body
无需请求体参数。
### 响应参数
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| appAuthToken | 应用授权令牌 | string |
| expiresIn | 令牌有效期(秒),-1 表示永久有效 | string |
### 请求示例
```bash
curl --location 'https://sbappstoreapi.ziniao.com/openapi-router/auth/get_app_token' \
--header 'Authorization: Bearer {API_Key}' \
--header 'Content-Type: application/json' \
--data-raw ''
```
### 响应示例
```json
{
"appAuthToken": "xxxxxxxxxxxxxxxx",
"expiresIn": "-1"
}
```
---
## 获取指定用户登录 Token
> 需联系紫鸟在线客服申请接口权限(走特殊申请 OA 单,Boss 验证后生效)。
> 目前仅支持 Windows 版客户端。
### 基本信息
| 项目 | 值 |
|------|------|
| 路径 | `/superbrowser/rest/v1/token/user-login` |
| 方法 | POST |
| Content-Type | application/json |
### 请求 Headers
| 参数名称 | 参数值 | 是否必须 |
|----------|--------|----------|
| Content-Type | application/json | true |
| Authorization | Bearer {API_Key} | true |
### 请求 Body
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|----------|----------|----------|----------|
| companyId | 公司 ID | false | number |
| userId | 用户 ID | false | number |
### 响应参数
> 注意:此接口业务层使用 `code`/`msg` 结构,与其他接口的 `ret`/`status`/`msg`/`count` 结构不同,判断成功应检查 `code == 0` 而非 `ret == 0`。
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| code | 0 表示成功 | number |
| msg | 状态描述(成功时为 "SUCCESS") | string |
| data | 响应数据对象 | object |
| data.token | 用户登录 token(即 openapiToken) | string |
| data.expireTime | 过期时长(秒) | number |
| data.timeUnit | 时间单位(SECONDS) | string |
### 请求示例
```bash
curl --location 'https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/token/user-login' \
--header 'Authorization: Bearer {API_Key}' \
--header 'Content-Type: application/json' \
--data-raw '{"companyId":"15393571083459","userId":"15393571087094"}'
```
### 响应示例
```json
{
"code": 0,
"msg": "SUCCESS",
"data": {
"token": "79db5b0c8d16d5d7eecded06101a7e39",
"expireTime": 1800,
"timeUnit": "SECONDS"
}
}
```
### 使用说明
获取到的 token 即为 Schema URL 中的 `openapiToken` 参数,拼接格式为:
```
superbrowser://OpenStrore?storeId={账号ID}&openapiToken={token}&userId={用户ID}
```
如果本地已登录紫鸟客户端,且 userId 与本地登录用户一致,可不传 openapiToken(客户端版本 v5.290 或 v6.25 以上)。
@@ -0,0 +1,256 @@
# 企业信息与员工/账号查询接口
> 本文件是 ziniao-sso-doc 的 Level 2 参考文档。
> 仅在需要查阅企业信息、员工查询或账号查询接口的详细参数时加载。
> **响应说明**:以下各接口的"响应参数"均为业务层字段,实际位于网关响应的 `data` 内部。完整响应结构见 `api-guide.md` 公共响应参数。
## 获取当前自建应用的公司信息
### 基本信息
| 项目 | 值 |
|------|------|
| 路径 | `/app/builtin/company` |
| 方法 | GET |
| Content-Type | application/json |
### 请求 Headers
| 参数名称 | 参数值 | 是否必须 |
|----------|--------|----------|
| Content-Type | application/json | true |
| Authorization | Bearer {API_Key} | true |
### 请求 Body
无。
### 响应参数
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| companyId | 企业 ID(固定值,获取一次后复用) | integer |
### 请求示例
```bash
curl --location 'https://sbappstoreapi.ziniao.com/openapi-router/app/builtin/company' \
--header 'Authorization: Bearer {API_Key}'
```
### 响应示例
```json
{
"companyId": 15393571083459
}
```
> companyId 为固定值,也可在开放平台首页右侧直接获取。
---
## ERP-员工查询
### 基本信息
| 项目 | 值 |
|------|------|
| 路径 | `/superbrowser/rest/v1/erp/staff/list` |
| 方法 | POST |
| Content-Type | application/json |
### 请求 Headers
| 参数名称 | 参数值 | 是否必须 |
|----------|--------|----------|
| Content-Type | application/json | true |
| Authorization | Bearer {API_Key} | true |
### 请求 Body
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|----------|----------|----------|----------|
| companyId | 公司 ID | true | number |
| page | 页码 | true | number |
| limit | 每页条数 | true | number |
| username | 用户账号 | false | string |
| name | 用户真实姓名 | false | string |
| departmentIds | 部门 ID 数组 | false | array\<number\> |
| level | 角色类型:0=Boss,1=经理,2=员工,3=超级管理员 | false | string |
| delflag | 删除标志:0=正常,1=禁用,2=删除(不传返回全部) | false | string |
| isAccurate | 查询模式:0=模糊查询,1=精确查询(默认 0) | false | number |
### 响应参数
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| ret | 状态码,0=成功 | number |
| status | success / faile | string |
| data | 员工列表 | array |
| data[].username | 用户名 | string |
| data[].userId | 用户 ID | number |
| data[].name | 联系人姓名 | string |
| data[].level | 角色类型:0=Boss,1=经理,2=员工,3=超级管理员 | string |
| data[].mobile | 手机号 | string |
| data[].delflag | 状态:0=正常,1=禁用,2=删除 | string |
| data[].authPhone | 登录手机号 | string |
| msg | 错误信息 | string |
| count | 总条数 | string |
### 请求示例
```bash
curl --location 'https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/staff/list' \
--header 'Authorization: Bearer {API_Key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"companyId": "15393571083459",
"page": "1",
"limit": "10",
"level": "",
"isAccurate": "",
"departmentIds": [],
"delflag": "",
"name": "",
"username": ""
}'
```
---
## ERP-账号列表查询
### 基本信息
| 项目 | 值 |
|------|------|
| 路径 | `/superbrowser/rest/v1/erp/store/list` |
| 方法 | POST |
| Content-Type | application/json |
### 请求 Headers
| 参数名称 | 参数值 | 是否必须 |
|----------|--------|----------|
| Content-Type | application/json | true |
| Authorization | Bearer {API_Key} | true |
### 请求 Body
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|----------|----------|----------|----------|
| companyId | 公司 ID | true | number |
| storeName | 账号名称 | false | string |
| ip | 设备 IP | false | string |
| page | 页码 | false | number |
| limit | 每页条数(私有化账号建议传 20) | false | number |
### 响应参数
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| ret | 状态码,0=成功 | number |
| status | success / faile | string |
| data | 账号列表 | array |
| data[].id | 账号 ID(即 storeId) | number |
| data[].ip | 设备 IP | string |
| data[].lastuserId | 最后登录用户 ID | number |
| data[].lastusetime | 最后登录时间 | string |
| data[].name | 账号名 | string |
| data[].platform | 账号所属平台(如"美国亚马逊") | string |
| data[].proxyId | 设备 ID | string |
| data[].siteId | 平台 ID | string |
| data[].username | 平台登录账号 | string |
| data[].createtime | 账号创建时间 | string |
| data[].siteName | 站点名称 | string |
| data[].packageName | 套餐名称 | string |
| data[].platformName | 平台名称 | string |
| msg | 错误信息 | string |
| count | 总条数 | string |
### 请求示例
```bash
curl --location 'https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/store/list' \
--header 'Authorization: Bearer {API_Key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"companyId": "15393571083459",
"storeName": "",
"ip": "",
"page": "1",
"limit": "10"
}'
```
---
## ERP-查询某用户有权限的账号列表
### 基本信息
| 项目 | 值 |
|------|------|
| 路径 | `/superbrowser/rest/v1/erp/user/stores` |
| 方法 | POST |
| Content-Type | application/json |
### 请求 Headers
| 参数名称 | 参数值 | 是否必须 |
|----------|--------|----------|
| Content-Type | application/json | true |
| Authorization | Bearer {API_Key} | true |
### 请求 Body
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|----------|----------|----------|----------|
| companyId | 公司 ID | true | number |
| userId | 用户 ID | true | number |
| storeName | 账号名称 | false | string |
| isAccurate | 查询模式:0=模糊,1=精确(默认精确) | false | number |
| page | 页码 | false | number |
| limit | 每页条数 | false | number |
### 响应参数
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| ret | 状态码,0=成功 | number |
| status | success / faile | string |
| data | 账号列表 | array |
| data[].id | 账号 ID(即 storeId) | number |
| data[].ipId | 设备 ID | number |
| data[].ip | 设备 IP | string |
| data[].platform | 账号所属平台 | string |
| data[].name | 账号名 | string |
| data[].lastUserId | 最后登录用户 ID | string |
| data[].lastUserName | 最后登录用户姓名 | string |
| data[].lastOpenUsername | 最后登录用户名称 | string |
| data[].lastUsetime | 最后登录时间 | string |
| data[].ipExpiryTime | 设备到期时间 | string |
| data[].tags | 账号标签列表 | array\<string\> |
| data[].regionInfo | 套餐信息 | string |
| data[].platformCategory | 平台类目 | string |
| data[].userName | 登录凭证 | string |
| msg | 错误信息 | string |
| count | 总条数 | number |
### 请求示例
```bash
curl --location 'https://sbappstoreapi.ziniao.com/openapi-router/superbrowser/rest/v1/erp/user/stores' \
--header 'Authorization: Bearer {API_Key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"companyId": "15393571083459",
"userId": "15393571087094",
"storeName": "",
"isAccurate": "",
"page": "1",
"limit": "10"
}'
```
@@ -0,0 +1,84 @@
# API 使用指南
> 本文件是 ziniao-sso-doc 的 Level 2 参考文档。
> 仅在需要了解 API 基地址、公共 Headers、公共响应结构或 curl 调用示例时加载。
## Base URL
```
https://sbappstoreapi.ziniao.com/openapi-router
```
所有服务端接口路径均以此为前缀拼接。
## 公共请求 Headers
| Header | 值 | 说明 |
|--------|------|------|
| Content-Type | application/json | 所有接口统一使用 JSON 格式 |
| Authorization | Bearer {API_Key} | API Key 从开放平台自建应用获取 |
## 认证方式
支持两种鉴权模式:
### 简单鉴权模式(推荐)
1. 在紫鸟开放平台创建"卖家自研应用"
2. API 身份验证选择"简单通用模式"
3. 获取 API Key
4. 每次请求在 Header 中携带 `Authorization: Bearer {API_Key}`
无需调用 `get_app_token` 接口,直接用 API Key 即可调用所有业务接口。
### 复杂鉴权模式
1. 创建应用后获取 API Key
2. 调用 `/auth/get_app_token` 获取 appAuthToken
3. 后续请求使用 appAuthToken 进行身份验证
## 公共响应参数
所有接口的网关层返回以下统一结构:
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| request_id | 全局请求 ID,用于追踪定位 | string |
| code | 网关返回码,`"0"` 表示成功 | string |
| msg | 网关返回码描述 | string |
| sub_code | 业务返回码,成功时不返回 | string |
| sub_msg | 业务返回码描述,成功时不返回 | string |
| data | 业务响应参数 | object |
### 业务层响应(data 内部)
部分接口(员工查询、账号查询等)的 data 内部有二级结构:
| 参数名称 | 参数说明 | 类型 |
|----------|----------|------|
| ret | 状态码,0=成功,其他=失败 | number |
| status | `"success"` 或 `"faile"` | string |
| data | 业务数据(数组或对象) | array/object |
| msg | 错误信息 | string |
| count | 总条数 | string/number |
### 响应判断逻辑
```
1. 检查网关层 code 是否为 "0"
├── 否 → 打印 request_id、msg、sub_msg,检查 API Key 和权限配置
└── 是 → 检查业务层是否成功
├── 否 → 打印响应获取错误原因
└── 是 → 解析 data 获取业务数据
```
> **异常处理要求**:请求异常(包括状态码异常)时,必须将 `request_id`、`msg` 和 `sub_msg` 都打印出来,便于追踪定位问题。
## 前置准备
1. 在紫鸟开放平台注册并创建"卖家自研应用"
2. 选择鉴权模式(简单鉴权或复杂鉴权)
3. 获取 API Key
4. 申请所需权限点(如需使用 `user-login` 接口,需联系紫鸟在线客服走特殊申请 OA 单,Boss 验证后生效)
5. 配置 IP 白名单
6. 获取 companyId(调用 `/app/builtin/company` 或在开放平台首页右侧查看)
@@ -0,0 +1,88 @@
# 客户端 Schema URL 协议
> 本文件是 ziniao-sso-doc 的 Level 2 参考文档。
> 仅在需要查阅 superbrowser:// Schema URL 的详细参数与用法时加载。
## 概述
通过 `superbrowser://` 自定义 URL 协议控制紫鸟浏览器,支持打开/关闭账号和退出程序。
调用方式有两种:
1. **浏览器链接**:在浏览器地址栏打开 `superbrowser://...`(需通过安装程序安装紫鸟)
2. **命令行**:`SuperBrowser.exe "superbrowser://..."` (需主程序路径正确)
---
## Action: OpenStrore(打开账号)
### 完整示例
```
superbrowser://OpenStrore?storeId=xxxxxxx&openapiToken=xxxxxxxxx&userId=xxxxxxx&lanuchUrl=aHR0cHM6Ly93d3cuYmFpZHUuY29t&autoopen=true&debuggPort=60001&notPromptForDownload=1&forceDownloadPath=Rjpc5a6J6KOF5YyF --disable-gpu start-maximized
```
### 必须参数
| 参数 | 说明 |
|------|------|
| storeId | 账号 ID |
| userId | 紫鸟的用户 ID(如果发生变化需要重新登录) |
| openapiToken | 鉴权 token(通过 `user-login` 接口获取) |
> 如果本地已登录紫鸟客户端,且 userId 与本地登录用户一致,openapiToken 可不传(客户端版本 v5.290 或 v6.25 以上)。
### 可选参数
| 参数 | 说明 |
|------|------|
| lanuchUrl | 将要打开的目标地址,需 Base64 编码(如 `https://www.baidu.com` → `aHR0cHM6Ly93d3cuYmFpZHUuY29t`) |
| autoopen | 设为 `true` 时自动打开 lanuchUrl 配置的地址 |
| debuggPort | 指定 debug 端口号 |
| notPromptForDownload | `1`=下载文件不弹出选择路径提示框,直接下载;`0` 或不传则弹出选择路径提示框 |
| forceDownloadPath | 设置默认下载路径,路径需 Base64 编码 |
### 命令行参数
命令参数添加在链接后面,用空格隔开,多个参数也用空格隔开。
> 添加的命令参数需要向管理员申请使用权限,否则不生效。
### 响应结果
启动紫鸟浏览器并打开对应账号。
---
## Action: exit(退出浏览器)
### 请求格式
```
superbrowser://exit
```
### 说明
退出紫鸟浏览器程序,关闭所有账号。
> 如果上次打开的程序未完全退出,下次调用打开账号将会发生异常。建议在重新打开前先调用 exit。
---
## Action: CloseStrore(关闭账号)
### 请求格式
```
superbrowser://CloseStrore?storeId=268682156400
```
### 参数
| 参数 | 说明 |
|------|------|
| storeId | 要关闭的账号 ID |
### 说明
关闭指定账号,每次只能关闭 1 个账号。如需关闭多个,需多次调用。
@@ -0,0 +1,163 @@
---
name: ziniao-webdriver-doc
description: >
Use when 需要理解紫鸟浏览器 WebDriver 自动化接口的定位、能力、架构或对接流程,
用于设计讨论、集成规划或工具评估。
不要用于执行具体自动化脚本编写——执行时直接参考 reference/ 中的接口与示例。
---
# ziniao-webdriver-doc(紫鸟 WebDriver 文档)
## 何时使用 / 不使用
| 场景 | 做法 |
|------|------|
| 需要理解紫鸟 WebDriver 是什么、能做什么 | 读本 Skill |
| 需要做自动化集成/框架选型的设计决策 | 读本 Skill |
| 需要查阅某个接口的请求/响应格式 | 读本 Skill 的 §按需深入 获取路径,再读对应 reference/ 文件 |
| 需要查看各语言/框架示例代码 | 不读本 Skill → 直接读 `reference/framework-examples.md` |
---
## Level 0:定位与边界
### 是什么
紫鸟浏览器 WebDriver 是紫鸟浏览器提供的本地自动化控制接口。通过命令行参数启动紫鸟浏览器进入 WebDriver 模式后,浏览器在本地开启 HTTP 服务,开发者可通过 JSON API 管理店铺窗口,并使用 Selenium / Puppeteer / Playwright / DrissionPage 等框架对接调试端口,实现对店铺浏览器的自动化控制。
### 能力概览
| 能力域 | 说明 |
|--------|------|
| 客户端启动控制 | 命令行参数启动/退出紫鸟浏览器进入 WebDriver 模式 |
| 认证与登录 | 通过 API 完成设备授权、账号登录、获取店铺列表 |
| 店铺窗口管理 | 启动/关闭指定店铺窗口,获取调试端口(debuggingPort) |
| 自动化框架对接 | 返回调试端口供 Selenium/Puppeteer/Playwright/DrissionPage 连接 |
| 内核管理 | 检测/下载/更新浏览器内核 |
| 缓存与会话 | 清理本地/在线缓存、管理 Cookie 策略 |
| 插件管理 | 启动店铺时加载指定插件、查询插件安装状态 |
### 不做什么(边界)
- 不是通用浏览器自动化方案——仅适用于紫鸟浏览器客户端
- 不提供 WebDriver 驱动本身——需根据返回的 `core_version` 自行下载匹配版本的 ChromeDriver
- 不处理电商平台业务逻辑——仅提供浏览器窗口控制能力
- 不提供云端部署——仅支持本地运行(通过 `--listen_ip` 可开放远程控制)
---
## Level 1:架构与流程
### 核心概念
**WebDriver 模式**:通过 `--run_type=web_driver` 参数启动紫鸟浏览器,使其进入自动化控制模式。此模式下浏览器在本地指定端口开启 HTTP 服务,接受 JSON 格式的 API 调用。启动前必须关闭紫鸟主进程。
**HTTP IPC 通信**:所有 API 均为 POST 请求,JSON 格式,UTF-8 编码。通过 `--port` 参数指定通信端口,地址为 `http://127.0.0.1:{port}`。每个请求需携带 `action` 字段标识操作类型、`requestId` 做全局唯一标识。超时建议 120 秒以上。
**店铺窗口(browserOauth / browserId)**:紫鸟以"店铺"为最小管理单位。调用 `getBrowserList` 获取店铺列表及加密 ID(`browserOauth`),然后通过 `startBrowser` 启动指定店铺窗口,返回 `debuggingPort` 用于自动化框架连接。若直接使用 `getBrowserList` 返回值打开店铺,只需传 `browserOauth`;仅当需要以明文店铺 ID 打开时才赋值 `browserId`。
**内核版本匹配**:`startBrowser` 返回 `core_type`(如 Chromium)和 `core_version`(如 119.1.0.16),开发者必须使用对应大版本号的 ChromeDriver(如 119.x)连接,否则自动化将失败。
### 流程全景
```mermaid
graph LR
A[申请权限] --> B["启动客户端<br>(先清理残留进程)"]
B --> C["updateCore<br>检测内核<br>⚠ 需凭据"]
C --> D["getBrowserList<br>获取店铺列表<br>⚠ 需凭据"]
D --> E["startBrowser<br>启动店铺窗口<br>⚠ 需凭据"]
E --> F["下载匹配的<br>ChromeDriver"]
F --> G["连接自动化框架<br>Selenium等"]
G --> H[执行自动化操作]
H --> I["stopBrowser<br>关闭店铺<br>⚠ 需凭据"]
I --> J[exit<br>退出客户端]
```
1. **申请权限**:在紫鸟开放平台开通 WebDriver 权限
2. **启动客户端**:先清理残留进程(见§关键陷阱),再命令行启动紫鸟浏览器,必须参数:`--run_type=web_driver --ipc_type=http --port={端口}`
3. **updateCore**:**必须携带 company/username/password**,轮询检测内核是否就绪(statusCode==0),缺失时自动下载
4. **getBrowserList**:传入 company/username/password,获取店铺列表
5. **startBrowser**:传入 company/username/password + browserOauth,返回 debuggingPort 和 core_version
6. **下载 ChromeDriver**:根据 `core_version` 大版本号下载匹配的 ChromeDriver(**不是系统 Chrome 版本**)
7. **连接框架**:用 `127.0.0.1:{debuggingPort}` + 匹配版本的 ChromeDriver 连接 Selenium 等
8. **关闭**:`stopBrowser`(需凭据)关闭店铺窗口,`exit` 退出客户端
### 能力摘要表
| 接口 Action | 职责 | 关键输入 | 关键输出 | 需凭据 |
|-------------|------|---------|---------|:------:|
| updateCore | 检测/下载内核 | **company, username, password** | statusCode, msg(进度) | **是** |
| applyAuth | 设备授权 | company, username, password | statusCode | **是** |
| getBrowserList | 获取店铺列表 | company, username, password | browserList[{browserOauth, browserName, siteId...}] | **是** |
| startBrowser | 启动店铺窗口 | company, username, password, browserOauth/browserId | debuggingPort, core_version, core_type | **是** |
| stopBrowser | 关闭店铺窗口 | company, username, password, browserOauth/browserId | statusCode | **是** |
| logout | 退出登录 | 无 | statusCode | 否 |
| exit | 退出客户端进程 | 无 | statusCode | 否 |
| ClearCache | 清理本地缓存 | browserOauths[] | statusCode | 否 |
| ClearOnline | 清理在线缓存 | company, username, password, browserOauth, type(1/2/3) | statusCode | **是** |
| getPluginInstalled | 查询插件安装状态 | browserId, pluginIds | install_status | 否 |
| getRunningInfo | 获取已开启的店铺 | 无 | browsers[] | 否 |
### 关键陷阱
> **以下均为实战中高频踩坑点,务必在编写自动化脚本时严格遵守。**
#### 陷阱 1:凭据一致性
所有标记"需凭据"的接口(`updateCore`、`getBrowserList`、`startBrowser`、`stopBrowser`、`ClearOnline`)**每次调用都必须携带完整的 company/username/password**,不只是"登录类"接口。`updateCore` 尤其容易遗漏——它看似只是内核检测,但紫鸟服务端需要凭据验证身份后才返回内核状态。
#### 陷阱 2:错误信息处理
API 返回错误时,**优先读取 `err` 字段的内容来定位问题**,而非仅依赖 `statusCode` 数字。`err` 字段包含具体的中文错误描述,是最有价值的排障信息。
编码注意:`err` 中的中文可能因响应体未正确按 UTF-8 解码而出现乱码,导致无法定位根因。**务必确保 HTTP 客户端以 UTF-8 解码响应体**(Python `requests` 库需设置 `response.encoding = 'utf-8'`,或使用 `response.content.decode('utf-8')`)。
#### 陷阱 3:进程残留清理
紫鸟启动失败或异常退出后,可能残留多个子进程(主进程 + 内核进程),如果不清理就重启会导致端口占用、反复失败。**每次启动前和失败后重试前,必须确保所有紫鸟相关进程已终止。**
清理命令:
```bash
# Windows (PowerShell)
Get-Process | Where-Object { $_.ProcessName -match 'ziniao|ZiNiao' } | Stop-Process -Force
# macOS / Linux
pkill -f ziniao || true
```
#### 陷阱 4:ChromeDriver 版本必须匹配紫鸟内核
紫鸟浏览器的内核版本(`core_version`)**与系统安装的 Chrome 浏览器版本无关**。必须根据 `startBrowser` 返回的 `core_version` 提取大版本号(如 `119.1.0.16` → `119`),下载对应版本的 ChromeDriver。
建议策略:
1. 首次获取 `core_version` 后,检查本地缓存目录是否已有该大版本的 ChromeDriver
2. 若无,从 https://googlechromelabs.github.io/chrome-for-testing/ 下载对应版本
3. 缓存到本地目录(如 `./chromedriver_cache/{major_version}/chromedriver`),后续复用
### 环境约定
| 项目 | 要求 |
|------|------|
| Windows | 紫鸟 V5 (5.X.X.X) 或 V6 (6.16.0.126+) |
| macOS | 紫鸟 V6 (6.15.0.44+) |
| Linux | 紫鸟 V6 (6.25.3.3+) |
| 通信协议 | HTTP POST, JSON, UTF-8 |
| 超时设置 | ≥ 120 秒 |
| 启动前提 | 紫鸟主进程必须已完全关闭 |
| 推荐框架 | Selenium(Playwright/Puppeteer 会被检测为自动化) |
---
## 按需深入(Level 2 路由表)
> **规则:以下内容不要主动加载。**
> 仅在你明确需要了解某个细节时,按路由表读取对应的 reference/ 文件。
| 你需要了解 | 读取文件 |
|-----------|---------|
| 核心工作流接口(applyAuth/getBrowserList/startBrowser) | `reference/api-core.md` |
| 辅助管理接口(stopBrowser/logout/exit/缓存/插件/updateCore) | `reference/api-auxiliary.md` |
| 启动客户端的命令行参数详细说明 | `reference/startup-params.md` |
| 各语言/框架的示例代码与下载链接 | `reference/framework-examples.md` |
| 权限开通、账号配置、常见问题排查 | `reference/prerequisites.md` |
@@ -0,0 +1,226 @@
# 辅助管理接口
> 本文件是 ziniao-webdriver-doc 的 Level 2 参考文档。
> 仅在需要查阅 stopBrowser / logout / exit / 缓存清理 / 插件查询 / updateCore 等辅助接口时加载。
## 4. stopBrowser — 关闭店铺窗口
**请求:**
```json
{
"company": "公司",
"username": "用户名",
"password": "密码",
"action": "stopBrowser",
"browserOauth": "店铺ID",
"browserId": "店铺数字ID(优先取此值)",
"requestId": "全局唯一标识",
"duplicate": 0
}
```
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"action": "stopBrowser",
"requestId": "全局唯一标识"
}
```
**状态码:** 0=成功,-10000=未知异常,-10002=参数非法,-10003=登录失败,-10013=需设备认证
---
## 5. logout — 退出登录
**请求:**
```json
{
"action": "logout",
"requestId": "全局唯一标识"
}
```
---
## 6. exit — 退出客户端进程
正常退出紫鸟浏览器主进程,会自动关闭已启动店铺并保持 Cookie 等信息。
**请求:**
```json
{
"action": "exit",
"requestId": "全局唯一标识"
}
```
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"action": "exit",
"requestId": "全局唯一标识"
}
```
**状态码:** 0=成功,-10005=登出失败
---
## 7. ClearCache — 清理本地缓存
不需要登录即可调用。
**请求:**
```json
{
"action": "ClearCache",
"browserOauths": ["店铺id1", "店铺id2"],
"requestId": "全局唯一标识"
}
```
`browserOauths` 为空则删除所有本地缓存。
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"action": "ClearCache",
"requestId": "全局唯一标识"
}
```
**状态码:** 0=成功,-10000=未知异常
---
## 8. ClearOnline — 清理在线缓存
**请求:**
```json
{
"company": "公司",
"username": "用户名",
"password": "密码",
"action": "ClearOnline",
"browserOauth": "店铺ID",
"type": 2,
"requestId": "全局唯一标识"
}
```
`type` 取值:1=清除所有缓存,2=仅保留cookie,3=仅保留二步认证cookie
**响应:**
```json
{
"statusCode": "状态码",
"statusMsg": "具体描述",
"err": "异常信息",
"action": "ClearOnline",
"requestId": "全局唯一标识"
}
```
**状态码:** 0=成功,-10000=没有找到记录
---
## 9. getPluginInstalled — 查询插件安装状态
**请求:**
```json
{
"action": "getPluginInstalled",
"requestId": "全局唯一标识",
"browserId": "店铺ID",
"duplicate": 0,
"pluginIds": "插件id,插件id"
}
```
**响应:**
```json
{
"statusCode": "状态码",
"statusMsg": "",
"err": "异常信息",
"action": "getPluginInstalled",
"requestId": "全局唯一标识",
"install_status": true
}
```
`install_status`:true=已通知安装,false=未通知
**状态码:** 0=成功,-10000=没有找到记录
---
## 10. getRunningInfo — 获取当前已开启的店铺
**请求:**
```json
{
"action": "getRunningInfo",
"requestId": "全局唯一标识"
}
```
**响应:**
```json
{
"statusCode": "状态码",
"browsers": [],
"requestId": "全局唯一标识"
}
```
**状态码:** 0=成功
---
## 11. updateCore — 更新内核
> **⚠ 此接口必须携带 company/username/password 凭据。** 遗漏凭据会导致请求失败——这是最常见的对接错误之一,因为 updateCore 看似只是"内核检测",容易被误认为不需要登录态。
因 HTTP 有超时限制,此接口适合循环调用直到返回成功。
**请求:**
```json
{
"company": "公司",
"username": "用户名",
"password": "密码",
"action": "updateCore",
"requestId": "全局唯一标识"
}
```
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"requestId": "全局唯一标识"
}
```
**状态码:** 0=成功,-10000=处理中
**调用机制:**
1. 客户端启动后调用 updateCore,若连接失败说明客户端还未启动成功,轮询(每2秒)直到连接成功
2. statusCode==0 → 内核就绪,继续后续操作
3. statusCode!=0 → 内核缺失/下载中,继续轮询并展示 `msg` 中的下载进度
4. 建议设置总超时 300 秒,避免无限等待
**错误排查:**
- 返回错误时**优先读取 `err` 字段**获取中文错误描述,而非仅看 statusCode 数字
- 若 `err` 内容出现乱码,说明 HTTP 响应体未正确按 UTF-8 解码——务必确保客户端以 UTF-8 解码响应
- 常见失败原因:未携带凭据、凭据错误、客户端尚未完全启动
@@ -0,0 +1,201 @@
# 核心工作流接口
> 本文件是 ziniao-webdriver-doc 的 Level 2 参考文档。
> 仅在需要查阅 applyAuth / getBrowserList / startBrowser 的请求/响应格式与状态码时加载。
## 概述
紫鸟 WebDriver 共提供 11 个 HTTP API,均为 POST 请求,JSON 格式,UTF-8 编码。
请求地址:`http://127.0.0.1:{port}`(port 为启动参数 `--port` 指定的端口)。
通用字段说明:
- `action`:接口标识(必须)
- `requestId`:全局唯一请求标识(必须)
- `statusCode`:返回状态码,`0` 表示成功
- `err`:异常信息
---
## 1. applyAuth — 申请设备授权
**请求:**
```json
{
"company": "公司",
"username": "用户名",
"password": "密码",
"action": "applyAuth",
"requestId": "全局唯一标识"
}
```
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"action": "applyAuth",
"requestId": "全局唯一标识"
}
```
**状态码:**
| 码值 | 说明 |
|------|------|
| 0 | 成功 |
| -10000 | 未知异常 |
| -10002 | Socket参数非法 |
| -10003 | 申请流程异常 |
| -10004 | 申请失败 |
---
## 2. getBrowserList — 获取店铺列表
**请求:**
```json
{
"company": "公司",
"username": "用户名",
"password": "密码",
"action": "getBrowserList",
"requestId": "全局唯一标识"
}
```
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"action": "getBrowserList",
"requestId": "全局唯一标识",
"browserList": [{
"browserOauth": "店铺ID(加密)",
"browserName": "店铺名称",
"browserIp": "店铺IP",
"siteId": "店铺所属站点",
"isExpired": false,
"platform_id": "平台id",
"platform_name": "平台名称"
}]
}
```
SiteId 说明文档:https://cdn-superbrowser-attachment.ziniao.com/webdriver/doc/SiteId%E8%AF%B4%E6%98%8E20250703.txt
**状态码:**
| 码值 | 说明 |
|------|------|
| 0 | 成功 |
| -10000 | 未知异常 |
| -10002 | Socket参数非法 |
| -10003 | 登录失败(需开通webdriver权限/关闭登录验证) |
| -10004 | 获取店铺列表时服务器返回异常 |
| -10013 | 需要设备认证 |
---
## 3. startBrowser — 启动店铺窗口
连续两次调用 startBrowser 会视为重启。关闭店铺需调用 stopBrowser。
**请求:**
```json
{
"company": "公司",
"username": "用户名",
"password": "密码",
"action": "startBrowser",
"browserOauth": "店铺ID(加密)",
"browserId": "店铺ID(未加密,优先取此值)",
"isHeadless": true,
"isWaitPluginUpdate": true,
"privacyMode": true,
"requestId": "全局唯一标识",
"runMode": "运行模式 1轻量/2均衡(默认)/3极速",
"pluginIdList": "id1,id2,id3",
"pluginIdType": 0,
"proxyTagFiter": "name1,name2,name3",
"cookieTypeLoad": 0,
"cookieTypeSave": 0,
"injectJsInfo": "{\"url\":\"http://...\",\"username\":\"用户名\"}",
"notPromptForDownload": 1,
"isLoadUserPlugin": true,
"windowRatio": 100,
"forceDownloadPath": "文件下载路径(绝对路径)",
"preSetting": "{\"profile.managed_default_content_settings.images\": 2}"
}
```
参数说明:
- `browserOauth` / `browserId`:二选一指定目标店铺。如果直接使用 `getBrowserList` 返回的数据来打开店铺,**只需传 `browserOauth` 即可**(它已包含加密店铺标识);仅当用户需要使用明文店铺 ID 打开时,才需要赋值 `browserId`。两者同时存在时 `browserId` 优先。
- `isHeadless`:无头模式(开启后需自行检测网络)
- `isWaitPluginUpdate`:等待插件热更新完成(仅 V5)
- `runMode`:仅 V5 支持
- `pluginIdType`:0=插件版本id,1=插件id
- `cookieTypeLoad`:固定传 0
- `cookieTypeSave`:0=默认,1=不提交
- `notPromptForDownload`:1=不弹窗,0=弹窗
- `windowRatio`:0-100,0=不控制,100=全屏
- `preSetting`:浏览器配置 JSON,支持无图模式
**响应:**
```json
{
"statusCode": "状态码",
"err": "异常信息",
"action": "startBrowser",
"browserOauth": "店铺ID",
"requestId": "全局唯一标识",
"LastError": "内部错误信息",
"ip": "代理IP",
"isDynamicIp": "是否动态IP",
"browserPath": "允许的下载路径",
"launcherPage": "平台默认启动页面",
"ipDetectionPage": "IP检测页地址",
"debuggingPort": "调试端口",
"reportPluginId": "报表插件id",
"duplicate": "副本标志",
"proxyTag": "当前启动的代理标志",
"proxyType": 5,
"mainHandle": "窗口句柄",
"core_version": "内核版本",
"core_type": "内核类型",
"coreVersion": "内核版本",
"coreType": "内核类型",
"downloadPath": "文件下载路径"
}
```
`proxyType` 取值:-1=错误数据,0=站群,1=云平台,2=自有IP,5=本地IP
使用 Selenium 连接时需根据 `core_type` 及 `core_version` 选取对应版本 ChromeDriver:
```java
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("debuggerAddress", "127.0.0.1:" + debuggingPort);
```
**状态码:**
| 码值 | 说明 |
|------|------|
| 0 | 成功 |
| -10000 | 未知异常 |
| -10001 | 内核窗口创建失败 |
| -10002 | Socket参数非法 |
| -10003 | 登录失败 |
| -10004 | browserOauth缺失 |
| -10006 | 上次 startBrowser 还未执行结束 |
| -10013 | 需要设备认证 |
| 1 | 初始化数据失败 |
| 2 | 当前IP无法正常使用 |
| 4 | 初始化时区失败 |
| 5 | 初始化代理失败 |
| 6 | 初始化黑白名单失败 |
| 7 | 启动内核失败 |
| 8 | 初始化浏览器个人目录失败 |
| 9 | 初始化Cookies失败 |
| 11 | 初始化浏览器设置文件失败 |
| 13 | 初始化代理信息配置失败 |
`LastError` 字段包含具体错误信息。
@@ -0,0 +1,159 @@
# 自动化框架示例
> 本文件是 ziniao-webdriver-doc 的 Level 2 参考文档。
> 仅在需要查看各语言/框架的示例代码与下载链接时加载。
## 概述
紫鸟 WebDriver 支持多种自动化框架和编程语言。所有示例代码均需替换以下信息后运行:
- 驱动程序路径(ChromeDriver 等)
- 客户端可执行程序路径
- Socket 端口号
- 用户登录信息(company, username, password)
**推荐使用 Selenium**:Playwright 和 Puppeteer 会被平台检测为自动化,需自行处理特征问题。
## 框架支持矩阵
| 框架 | 语言 | 示例文件 | 下载链接 | 备注 |
|------|------|---------|---------|------|
| Selenium | Python | ziniao_webdriver_http_py3.py | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/ziniao_webdriver_http_py3.py) | 基础店铺访问示例,适合初学者 |
| Selenium | Python | ziniao_webdriver_demo | [GitHub](https://github.com/ziniao-open/ziniao_webdriver_demo) | 开源示例集:订单拉取、报表下载、评价导出 |
| Selenium | Java | webdriver-java-demo.zip | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/webdriver-java-demo.zip) | 用 IDEA 打开后运行 |
| Selenium | C#.NET | webdriver-C#.net-demo.zip | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/webdriver-CSharp-demo.zip) | Windows 环境 |
| Puppeteer | JavaScript | puppeteer_js_demo.zip | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/puppeteer_js_demo.zip) | 通过 Puppeteer API 控制 |
| Playwright | Python | ziniao_playwright_http_py3.py | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/ziniao_playwright_http_py3.py) | 会被检测为自动化,建议用 Selenium |
| Playwright | Java | playwright-java-demo.zip | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/playwright-java-demo.zip) | 会被检测为自动化,建议用 Selenium |
| Playwright | JavaScript | playwright_js_demo.zip | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/playwright_js_demo.zip) | 会被检测为自动化,建议用 Selenium |
| DrissionPage | Python | ziniao_drissionpage_http_py3.py | [下载](https://cdn-superbrowser-attachment.ziniao.com/webdriver/demo/20260119/ziniao_drissionpage_http_py3.py) | 通过 DrissionPage API 控制 |
## 连接示例(以 Selenium 为例)
### Python + Selenium
```python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
# startBrowser 返回的 debuggingPort 和 core_version
debugging_port = 12345
core_version = "119.1.0.16"
# 根据 core_version 的大版本号选择对应 ChromeDriver
chrome_driver_path = "/path/to/chromedriver-119"
options = Options()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debugging_port}")
service = Service(executable_path=chrome_driver_path)
driver = webdriver.Chrome(service=service, options=options)
# 现在可以操作店铺浏览器窗口
print(driver.title)
```
### Java + Selenium
```java
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("debuggerAddress", "127.0.0.1:" + debuggingPort);
// 根据 core_version 选择对应版本 ChromeDriver
System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver-" + majorVersion);
WebDriver driver = new ChromeDriver(options);
System.out.println(driver.getTitle());
```
## ChromeDriver 版本匹配
> **⚠ 紫鸟内核版本 ≠ 系统 Chrome 版本。** 不要使用系统安装的 Chrome 浏览器对应的 ChromeDriver,必须根据紫鸟 `startBrowser` 返回的 `core_version` 来匹配。
`startBrowser` 响应中的 `core_type` 和 `core_version` 决定了需要使用的 ChromeDriver 版本:
- `core_type: "Chromium"`, `core_version: "119.1.0.16"` → 使用 ChromeDriver 119.x
- 下载地址:https://googlechromelabs.github.io/chrome-for-testing/
- JSON API(自动化脚本可直接解析):https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json
版本不匹配会导致连接失败,务必检查。
### 自动下载与缓存策略(Python 伪代码)
```python
import os, re, json, zipfile, io, platform
import requests
CACHE_DIR = "./chromedriver_cache"
def get_chromedriver_path(core_version: str) -> str:
"""根据紫鸟 core_version 获取匹配的 ChromeDriver 路径,自动下载缓存。"""
major = core_version.split(".")[0] # "119.1.0.16" → "119"
exe_name = "chromedriver.exe" if platform.system() == "Windows" else "chromedriver"
cached = os.path.join(CACHE_DIR, major, exe_name)
if os.path.exists(cached):
return cached
# 查询可用版本
resp = requests.get(
"https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
)
resp.encoding = "utf-8"
data = resp.json()
# 找到该大版本的最新 chromedriver
plat = {
"Windows": "win64", "Darwin": "mac-x64", "Linux": "linux64"
}[platform.system()]
download_url = None
for v in reversed(data["versions"]):
if v["version"].startswith(major + ".") and "chromedriver" in v["downloads"]:
for d in v["downloads"]["chromedriver"]:
if d["platform"] == plat:
download_url = d["url"]
break
if download_url:
break
if not download_url:
raise RuntimeError(f"未找到大版本 {major} 的 ChromeDriver 下载地址")
# 下载并解压
os.makedirs(os.path.join(CACHE_DIR, major), exist_ok=True)
zip_resp = requests.get(download_url)
with zipfile.ZipFile(io.BytesIO(zip_resp.content)) as zf:
for name in zf.namelist():
if name.endswith(exe_name):
with open(cached, "wb") as f:
f.write(zf.read(name))
break
if platform.system() != "Windows":
os.chmod(cached, 0o755)
return cached
```
### 缓存目录结构
```
chromedriver_cache/
├── 119/
│ └── chromedriver.exe
├── 120/
│ └── chromedriver.exe
└── ...
```
## GitHub 开源示例项目
仓库地址:https://github.com/ziniao-open/ziniao_webdriver_demo
包含以下场景示例:
1. **Demo 1**:电商订单自动拉取(热门推荐)
2. **Demo 2**:销售与流量业务报表下载
3. **Demo 3**:电商平台用户评价导出
克隆后替换配置信息即可运行。
@@ -0,0 +1,141 @@
# 前置条件与排障
> 本文件是 ziniao-webdriver-doc 的 Level 2 参考文档。
> 仅在需要了解权限开通、账号配置、常见问题排查时加载。
## 概述
使用紫鸟 WebDriver 前需完成权限开通和环境准备。本文档覆盖权限申请、账号配置和常见问题排查。
## 1. 开通 WebDriver 权限
首次使用 WebDriver 时,需先在紫鸟开放平台开通权限。
详细步骤见官方文档:[如何开通webdriver权限](https://open.ziniao.com/docSupport?docId=99)
## 2. 客户端版本要求
| 操作系统 | 最低版本 |
|---------|---------|
| Windows | 紫鸟 V5 (5.X.X.X) 或 V6 (6.16.0.126+) |
| macOS | 紫鸟 V6 (6.15.0.44+) |
| Linux | 紫鸟 V6 (6.25.3.3+) |
## 3. 环境准备清单
### 3.1 关闭主进程
启动 WebDriver 前**务必**确保紫鸟浏览器主进程已完全关闭,避免端口占用或冲突。
### 3.2 清理残留进程
紫鸟启动失败或异常退出后,可能残留多个子进程(主进程 + 内核进程),不清理直接重启会导致端口占用、反复失败。**每次启动前和失败后重试前,都必须执行清理。**
已知的紫鸟相关进程名:`ziniao`、`ZiNiao`、`ziniao.exe`、`ZiNiaoBrowser` 等(不同版本命名可能不同,建议用模糊匹配)。
清理命令:
```bash
# Windows (PowerShell)
Get-Process | Where-Object { $_.ProcessName -match 'ziniao|ZiNiao' } | Stop-Process -Force
# macOS / Linux
pkill -f ziniao || true
```
验证清理结果:
```bash
# Windows (PowerShell) — 应无输出
Get-Process | Where-Object { $_.ProcessName -match 'ziniao|ZiNiao' }
# macOS / Linux — 应无输出
pgrep -f ziniao
```
### 3.3 创建自动化专用账号
- 创建专用的自动化成员账号(命名建议:`自动化_Robot`)
- 正式使用前验证该账号能否正常登录紫鸟浏览器客户端
### 3.4 配置账号权限
**问题场景**:自动化账号登录后无法获取店铺列表。
**解决方案**:
1. 登录企业管理后台
2. 检查该账号的权限配置
3. 授予查看对应账号/店铺的权限
4. 建议新建包含此权限的自定义角色并分配给自动化账号
### 3.5 超时设置
将所有 HTTP 请求的超时时间设为 **120 秒或以上**,因为启动店铺环境可能耗时较长。
### 3.6 系统资源管理
- 启动店铺窗口 CPU 消耗较大
- 根据设备配置合理控制并发启动数量
- 建议错开各任务的启动时间,避免瞬时负载过高
### 3.7 ChromeDriver 版本管理
> **关键:紫鸟内核版本 ≠ 系统 Chrome 版本。** 必须根据 `startBrowser` 返回的 `core_version` 匹配 ChromeDriver,不能使用系统 Chrome 对应的版本。
**匹配规则:**
- `core_version` 格式为 `X.Y.Z.W`(如 `119.1.0.16`),取第一段 `X` 作为大版本号
- 下载该大版本号对应的 ChromeDriver(如大版本 `119` → ChromeDriver 119.x)
**下载地址:**
- Chrome for Testing:https://googlechromelabs.github.io/chrome-for-testing/
- JSON API(便于自动化脚本解析):https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json
**本地缓存建议:**
```
chromedriver_cache/
├── 119/
│ └── chromedriver.exe # 对应 core_version 119.x
├── 120/
│ └── chromedriver.exe # 对应 core_version 120.x
└── ...
```
自动化脚本应实现以下逻辑:
1. 从 `startBrowser` 响应中提取 `core_version` 大版本号
2. 检查缓存目录 `chromedriver_cache/{major_version}/` 是否存在
3. 若不存在,从 Chrome for Testing 下载对应版本并解压到缓存目录
4. 使用缓存中的 ChromeDriver 路径连接 Selenium
## 4. 通信注意事项
1. 请求/响应均为 JSON 格式,收发消息统一 UTF-8 编码
2. HTTP 模式下超时时长必须 ≥120 秒
3. 调用任何接口后务必检查 `statusCode`(0 表示成功),**出错时优先读取 `err` 字段获取中文错误描述**,这是最有价值的排障信息
4. **启动后必须首先调用 `updateCore` 接口**(需携带凭据),否则打开店铺可能出现内核缺失
5. **务必确保 HTTP 客户端以 UTF-8 解码响应体**,否则 `err` 中的中文会乱码,严重影响错误定位(Python `requests` 库需设置 `response.encoding = 'utf-8'`)
6. 所有标记"需凭据"的接口(`updateCore`、`getBrowserList`、`startBrowser`、`stopBrowser`、`ClearOnline`)每次调用都必须携带 company/username/password
## 5. 常见问题
| 现象 | 可能原因 | 解决方案 |
|------|---------|---------|
| 连接接口失败 | 客户端未启动完成 | 轮询 updateCore 直到连接成功 |
| getBrowserList 返回 -10003 | 未开通权限或未关闭登录验证 | 检查权限开通状态 |
| getBrowserList 返回 -10013 | 新设备未授权 | 调用 applyAuth 进行设备授权 |
| 店铺列表为空 | 账号无店铺权限 | 在企业管理后台配置角色权限 |
| startBrowser 返回 -10006 | 上次启动未完成 | 等待或调用 stopBrowser 后重试 |
| startBrowser 返回 7 | 内核缺失 | 确保先调用 updateCore 等待成功 |
| Selenium 连接失败 | ChromeDriver 版本不匹配 | 根据 core_version 下载匹配版本 |
| 请求超时 | 超时设置过短 | 设置超时 ≥120 秒 |
| 多账号冲突 | 端口/缓存冲突 | V5 使用 `--multip` + `--enforce-cache-path` |
| updateCore 返回错误 | 未携带凭据(company/username/password) | 所有需凭据的接口必须携带完整凭据 |
| err 字段内容乱码 | HTTP 响应体未以 UTF-8 解码 | 设置 `response.encoding = 'utf-8'` 或用 `response.content.decode('utf-8')` |
| 重启失败 / 端口占用 | 上次失败后残留紫鸟进程 | 清理所有紫鸟相关进程后重试(见§3.2) |
| Selenium 连接失败(版本不匹配) | 使用了系统 Chrome 对应的 ChromeDriver | 必须根据紫鸟 `core_version` 大版本号下载匹配的 ChromeDriver(见§3.7) |
## 6. 技术支持
如在对接过程中遇到问题,可通过以下渠道获取支持:
- 微信添加紫鸟生态管家,备注 "紫鸟webdriver"
- 官方 FAQ:https://open.ziniao.com/docSupport?docId=257
@@ -0,0 +1,122 @@
# 启动参数详细说明
> 本文件是 ziniao-webdriver-doc 的 Level 2 参考文档。
> 仅在需要了解紫鸟浏览器 WebDriver 模式的启动命令行参数时加载。
## 概述
通过命令行参数启动紫鸟浏览器客户端,使其进入 WebDriver 模式。
启动前**必须**关闭紫鸟浏览器的主进程,否则可能端口占用或冲突。
## 必选参数
| 参数 | 版本支持 | 说明 |
|------|---------|------|
| `--run_type=web_driver` | V5 / V6 全平台 | 指定以 WebDriver 模式运行主进程 |
| `--ipc_type=http` | V5 / V6 全平台 | 约定通信方式为 HTTP,超时建议 ≥120 秒 |
| `--port=端口号` | V5 / V6 全平台 | 指定通讯端口,紫鸟在本机以此端口启动 HTTP 服务 |
## 可选参数
| 参数 | 版本支持 | 说明 |
|------|---------|------|
| `--port_exception=1886,1567` | V5 only | 店铺内允许连接到本地的端口号,逗号分隔 |
| `--multip` | V5 only | 去除干扰自动化的功能,多账号建议与 `--enforce-cache-path` 一起使用 |
| `--enforce-cache-path=路径` | V5 only | 指定独立缓存目录,允许多账号同时运行,需自行调整 `--port` 避免冲突 |
| `--listen_ip=IP` | V5 only | 设置监听地址,默认 127.0.0.1。非本机时允许远程控制 |
| `--dns=DNS服务器` | V5 only | 设置 DNS,默认使用本机 DNS。示例:`--dns=223.5.5.5` |
## 启动命令示例
### Python
```python
import subprocess, platform, time
client_path = "紫鸟浏览器可执行文件路径"
socket_port = 9515
is_windows = platform.system() == 'Windows'
is_mac = platform.system() == 'Darwin'
is_linux = platform.system() == 'Linux'
def start_browser():
try:
if is_windows:
cmd = [client_path,
'--run_type=web_driver',
'--ipc_type=http',
'--port=' + str(socket_port)]
elif is_mac:
cmd = ['open', '-a', client_path, '--args',
'--run_type=web_driver',
'--ipc_type=http',
'--port=' + str(socket_port)]
elif is_linux:
cmd = [client_path, '--no-sandbox',
'--run_type=web_driver',
'--ipc_type=http',
'--port=' + str(socket_port)]
else:
exit()
subprocess.Popen(cmd)
time.sleep(5)
except Exception:
import traceback
print('start browser process failed: ' + traceback.format_exc())
exit()
```
### JavaScript (Node.js)
```javascript
const childProcess = require('child_process');
const util = require('util');
const os = require('os');
const execFile = util.promisify(childProcess.execFile);
const clientPath = '紫鸟浏览器可执行文件路径';
const socketPort = 9515;
const is_windows = os.platform() === 'win32';
const is_mac = os.platform() === 'darwin';
const is_linux = os.platform() === 'linux';
async function startBrowser() {
try {
if (is_windows) {
const cmd = [clientPath,
'--run_type=web_driver',
'--ipc_type=http',
`--port=${socketPort}`];
execFile(cmd[0], cmd.slice(1));
} else if (is_mac) {
const cmd = ['open', '-a', clientPath, '--args',
'--run_type=web_driver',
'--ipc_type=http',
`--port=${socketPort}`];
execFile(cmd[0], cmd.slice(1));
} else if (is_linux) {
const cmd = [clientPath, '--no-sandbox',
'--run_type=web_driver',
'--ipc_type=http',
`--port=${socketPort}`];
execFile(cmd[0], cmd.slice(1));
} else {
process.exit();
}
await new Promise(resolve => setTimeout(resolve, 5000));
} catch (error) {
console.log(`start browser process failed: ${error}`);
process.exit();
}
}
```
## 各操作系统差异
| 操作系统 | 启动方式 | 特殊参数 |
|---------|---------|---------|
| Windows | 直接调用可执行文件 | 无 |
| macOS | 通过 `open -a` 启动,`--args` 传参 | 无 |
| Linux | 直接调用可执行文件 | 需追加 `--no-sandbox` |
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 ziniao-open
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.
+244
View File
@@ -0,0 +1,244 @@
# ziniao-skills
紫鸟浏览器 AI Agent Skill 包,覆盖 ERP 管理(员工/角色/设备/标签/部门)和店铺自动化操作,适配 ziniao-cli 与 ZClaw 本地接口。
## Skill 安装
以后 Skill 内置在 `ziniao-cli` 里安装,不再单独走 `npx skills add`:
```bash
ziniao-cli skills install
```
先安装 CLI 后再执行上面的命令。备选(不推荐):`npx skills add ziniao-open/skills -y -g`
## ziniao-cli 安装
```bash
npm install -g @ziniao-open/cli
```
安装后验证:
```bash
ziniao-cli --version
```
## 快速开始
### 1. 初始化配置
```bash
ziniao-cli config init
```
按提示选择"新建应用"或"手动输入 API Key",完成后凭证自动存入系统 Keychain。
### 2. AI Agent 初始化流程
AI Agent 应使用非交互模式:
```bash
ziniao-cli config init --new
```
该命令会阻塞直到审核通过、被拒绝或超时(1 小时),Agent 的处理流程:
1. 后台执行 `config init --new`
2. 从 stderr 输出中提取 URL(包含 `memberAuth?cliRequestId=` 的行)
3. 将链接展示给用户,提示在浏览器中打开完成应用创建
4. 等待命令完成(审核通过/拒绝/超时)
输出示例:
```
请在浏览器中打开以下链接完成应用创建:
https://open.ziniao.com/memberAuth?cliRequestId=a1b2c3d4-...&from=cli
⏳ 等待应用创建及审核... (按 Ctrl+C 取消)
✓ 审核已通过
✓ 企业 ID: 15393*****3459
✓ 配置已保存 (profile: zhangsan)
```
初始化完成后,`isBoss` 字段决定账号权限范围:
| 账号类型 | 服务端 API | ZClaw Bridge(本地浏览器) |
|---------|-----------|--------------------------|
| Boss | 全部可用 | 全部可用 |
| 成员 | 不可用 | 全部可用 |
可通过 `ziniao-cli config show` 查看当前账号类型。
### 3. 多账号管理
支持多个账号配置(profile),通过 `config use` 切换:
```bash
# 列出所有 profile
ziniao-cli config list
# 切换到指定 profile
ziniao-cli config use <name>
# 重命名 profile
ziniao-cli config rename <old> <new>
# 删除 profile(需确认,--yes 跳过)
ziniao-cli config remove --profile <name>
```
### 4. 验证连通性
```bash
ziniao-cli doctor
```
### 5. 开始使用
```bash
# 查看员工列表
ziniao-cli staff list --format table
# 查看部门树
ziniao-cli department list --tree
# 查看角色列表
ziniao-cli role list --format table
```
## 命令概览
### 配置管理
| 命令 | 说明 |
|------|------|
| `config init` | 初始化配置 |
| `config show` | 显示当前配置 |
| `config list` | 列出所有 profile |
| `config use <name>` | 切换当前 profile |
| `config rename <old> <new>` | 重命名 profile |
| `config remove` | 删除 profile(需确认) |
### 部门管理
| 命令 | 说明 |
|------|------|
| `department list` | 查询部门列表 |
| `department create` | 新增部门 |
| `department update` | 修改部门 |
| `department delete` | 删除部门 |
| `department reorder` | 部门排序 |
### 员工管理
| 命令 | 说明 |
|------|------|
| `staff list` | 查询员工列表 |
| `staff create` | 新增员工 |
| `staff update` | 修改员工信息 |
| `staff enable` | 启用员工 |
| `staff disable` | 禁用员工 |
| `staff remove` | 删除员工 |
| `staff transfer` | 员工调岗 |
### 角色管理
| 命令 | 说明 |
|------|------|
| `role list` | 查询角色列表 |
| `role detail` | 查看角色详情 |
| `role create` | 创建角色 |
| `role update` | 修改角色 |
| `role assign` | 分配角色给员工 |
| `role staff-roles` | 查看员工已分配角色 |
| `role permissions` | 查看可用权限列表 |
### 账号(店铺)管理
| 命令 | 说明 |
|------|------|
| `account list` | 查询账号列表 |
| `account create` | 创建账号 |
| `account update` | 修改账号信息 |
| `account delete` | 删除账号 |
| `account auth-add` | 授权员工访问店铺 |
| `account auth-remove` | 撤销员工授权 |
| `account auth-clean` | 清空店铺所有授权 |
| `account auth-list` | 查询员工被授权的店铺 |
| `account auth-users` | 查询店铺已授权的员工 |
| `account tag-list` | 查询企业标签列表 |
| `account tag-create` | 创建标签 |
| `account tag-rename` | 重命名标签 |
| `account tag-delete` | 删除标签 |
| `account tag-bind` | 绑定标签到店铺 |
| `account tag-unbind` | 解绑标签 |
| `account tag-replace` | 全量替换店铺标签 |
| `account tag-clear` | 清空店铺标签 |
| `account tag-remove` | 从店铺移除标签 |
| `account tag-of-store` | 查询店铺绑定的标签 |
### 设备管理
| 命令 | 说明 |
|------|------|
| `device list` | 查询设备列表 |
| `device add-custom` | 添加自有设备 |
| `device update-custom` | 修改自有设备 |
| `device plan-list` | 查询可购买套餐 |
| `device bind` | 绑定设备到店铺 |
| `device unbind` | 解绑设备 |
| `device auto-renew` | 设置自动续费 |
| `device purchase` | 购买设备套餐 |
| `device renew` | 续费设备 |
### 浏览器控制(ZClaw)
| 命令 | 说明 |
|------|------|
| `store list` | 列出已打开的店铺浏览器 |
| `store open` | 打开店铺浏览器 |
| `store close` | 关闭店铺浏览器 |
| `page visit` | 导航到指定 URL |
| `page screenshot` | 页面截图 |
| `page automation` | 页面自动化操作 |
### 通用 API 调用
对于未提供快捷命令的接口(共 73 个接口中 shortcut 覆盖 45 个),可使用通用 `api` 命令:
```bash
ziniao-cli api <path> [--data '{}'] [--format table] [--jq '.field']
ziniao-cli api /superbrowser/rest/v1/erp/store/deletecookie --data '{"storeId":"xxx"}'
```
## Skill 模块
| Skill | 说明 |
|-------|------|
| `ziniao-shared` | 共享基础:配置初始化、认证、输出格式、权限点 |
| `ziniao-staff` | 员工管理 |
| `ziniao-role` | 角色与权限管理 |
| `ziniao-department` | 部门管理 |
| `ziniao-account` | 账号(店铺)管理与标签 |
| `ziniao-device` | 设备管理 |
| `ziniao-store` | 浏览器店铺控制(ZClaw) |
| `ziniao-page` | 浏览器页面操作(ZClaw) |
| `ziniao-access-policy` | 访问策略管理 |
| `ziniao-openapi-explorer` | API 路径清单与探索指引 |
| `ziniao-workflow-batch-account` | 批量账号授权工作流 |
| `ziniao-skill-maker` | 自定义 Skill 创建指引 |
## 适用环境
这些 Skill 文档适用于任何支持工具调用的 AI Agent 环境:
- Claude Code
- Cursor
- OpenClaw
- 其他支持 Skill 协议的 Agent
## License
CC BY 4.0
@@ -0,0 +1,183 @@
---
name: ziniao-access-policy
version: 1.0.0
description: "紫鸟访问策略管理:网页访问规则、网页/网页分组、规则生效成员/账号、账号绑定策略的查询和维护。当前无快捷命令,全部通过 ziniao-cli api 直调。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 访问策略管理
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)**
访问策略接口用于管理 ERP 网页访问权限,包括访问规则、网页配置、网页分组,以及账号与访问策略的绑定关系。
当前没有 `access-policy` 快捷命令,全部使用通用 `api` 命令直调。所有接口均为 `POST`、`application/json`,请求体需要显式传 `companyId`。
## 关键枚举
| 字段 | 值 | 说明 |
|------|----|------|
| `activeUser` | `1` | 除 boss 外所有成员 |
| `activeUser` | `2` | 指定成员 |
| `activeUser` | `3` | 指定部门 |
| `activeUser` | `4` | 指定角色 |
| `activeAccount` | `0` | 所有账号 |
| `activeAccount` | `1` | 指定账号 |
| `activeRange` | `1` | 所有网站 |
| `activeRange` | `2` | 指定网址-黑名单 |
| `activeRange` | `3` | 指定网址-白名单 |
| `isActive` | `0` | 禁用 |
| `isActive` | `1` | 启用 |
## 访问规则
| 操作 | API |
|------|-----|
| 规则列表 | `/superbrowser/rest/v1/erp/security/access_rule/list` |
| 规则详情 | `/superbrowser/rest/v1/erp/security/access_rule/detail` |
| 新建规则 | `/superbrowser/rest/v1/erp/security/access_rule/add` |
| 编辑规则 | `/superbrowser/rest/v1/erp/security/access_rule/edit` |
| 删除规则 | `/superbrowser/rest/v1/erp/security/access_rule/delete` |
| 启用/禁用规则 | `/superbrowser/rest/v1/erp/security/access_rule/active` |
| 设置生效成员 | `/superbrowser/rest/v1/erp/security/access_rule/change_user` |
| 设置生效账号 | `/superbrowser/rest/v1/erp/security/access_rule/change_account` |
| 规则可绑定账号列表 | `/superbrowser/rest/v1/erp/security/access_rule/account/bindable_list` |
```bash
# 查询访问规则
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/list \
--data '{"companyId":123,"page":1,"limit":20}'
# 查看规则详情
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/detail \
--data '{"companyId":123,"ruleId":456}'
# 删除规则,ruleIds 是数组
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/delete \
--data '{"companyId":123,"ruleIds":[456]}'
# 启用规则,禁用时 isActive 传 "0"
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/active \
--data '{"companyId":123,"ruleIds":[456],"isActive":"1"}'
```
设置生效成员接口无快捷命令,需通过 `api` 直调。请求体字段必须按接口文档发送:
| 字段 | 类型 | 说明 |
|------|------|------|
| `ruleId` | number | 规则 ID |
| `activeUser` | number | `1` 除 boss 外所有成员;`2` 指定成员;`3` 指定部门;`4` 指定角色 |
| `userChange.addIds` | string[] | 新增成员 ID 列表;无新增传 `[]` |
| `userChange.addNames` | string[] | 新增成员用户名列表;无新增传 `[]` |
| `userChange.deleteIds` | string[] | 删除成员 ID 列表;无删除传 `[]` |
| `userChange.deleteNames` | string[] | 删除成员用户名列表;无删除传 `[]` |
| `departmentIds` | number[] | 指定部门列表;非部门模式也传 `[]` |
| `roleIds` | number[] | 指定角色列表;非角色模式也传 `[]` |
`addIds` 和 `addNames` 可任选一个传,两者都传时取并集;`deleteIds/deleteNames` 不能与新增列表重复。`companyId` 由 CLI 自动注入,不要在 `--data` 中手动传入公共参数。
```bash
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/change_user \
--data '{"companyId":123,"ruleId":456,"activeUser":2,"userChange":{"addIds":["1001"],"deleteIds":[],"addNames":[],"deleteNames":[]},"departmentIds":[],"roleIds":[]}'
```
设置生效账号时,`accountChange.addIds` 和 `accountChange.deleteIds` 传账号 ID 字符串数组。
```bash
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/change_account \
--data '{"companyId":123,"ruleId":456,"activeAccount":1,"accountChange":{"addIds":["2001"],"deleteIds":[]}}'
```
新建/编辑规则的请求体较复杂,核心结构如下:
```json
{
"companyId": 123,
"name": "限制文件下载",
"desc": "运营角色适用",
"activeUser": 2,
"userChange": { "addIds": ["1001"], "addNames": [] },
"departmentIds": [],
"roleIds": [],
"activeAccount": 0,
"accountChange": { "addIds": [] },
"activeRange": "3",
"configs": [
{
"urlId": 789,
"isAccessible": true,
"isLog": true,
"functions": [
{ "function": "file_download", "permission": 3 }
]
}
],
"isSkipRuleConflictCheck": false,
"effectiveTime": { "timeType": "0", "startTime": 0, "endTime": 0 }
}
```
## 网页与网页分组
| 操作 | API |
|------|-----|
| 网页列表 | `/superbrowser/rest/v1/erp/security/access_rule_url/list` |
| 网页详情 | `/superbrowser/rest/v1/erp/security/access_rule_url/detail` |
| 添加网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/add` |
| 编辑网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/edit` |
| 删除网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/delete` |
| 网页修改分组 | `/superbrowser/rest/v1/erp/security/access_rule_url/change_group` |
| 网页分组列表 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/list` |
| 添加网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/add` |
| 编辑网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/edit` |
| 删除网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/delete` |
```bash
# 查询网页列表
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule_url/list \
--data '{"companyId":123,"page":1,"limit":20}'
# 添加网页
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule_url/add \
--data '{"companyId":123,"name":"Amazon后台","groupId":10,"urlPattern":"https://sellercentral.amazon.com/*","desc":"亚马逊后台"}'
# 移动网页到分组,urlIds 是数组
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule_url/change_group \
--data '{"companyId":123,"groupId":10,"urlIds":[789]}'
# 查询分组树,isAll=true 时返回 children
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule_url_group/list \
--data '{"companyId":123,"groupIdParent":0,"isAll":true}'
```
## 账号绑定策略
| 操作 | API |
|------|-----|
| 账号绑定的访问策略列表 | `/superbrowser/rest/v1/erp/security/access_rule/list/one_account` |
| 账号添加访问策略 | `/superbrowser/rest/v1/erp/security/access_rule/account_ref/add_to_account` |
| 账号移除访问策略 | `/superbrowser/rest/v1/erp/security/access_rule/account_ref/remove_from_account` |
```bash
# 查询某账号已绑定的策略
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/list/one_account \
--data '{"companyId":123,"accountId":"2001","page":1,"limit":20}'
# 给账号添加访问策略
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/account_ref/add_to_account \
--data '{"companyId":123,"accountId":"2001","ruleIds":[456],"isSkipRuleConflictCheck":false}'
# 从账号移除访问策略
ziniao-cli api /superbrowser/rest/v1/erp/security/access_rule/account_ref/remove_from_account \
--data '{"companyId":123,"accountId":"2001","ruleIds":[456]}'
```
## 注意事项
- `ruleIds`、`urlIds`、`platformIds`、`tagIds`、`departmentIds`、`roleIds` 都是数组,单个值也要包装成数组。
- 成员相关 `addIds/deleteIds/addNames/deleteNames` 是字符串数组;账号相关 `accountChange.addIds/deleteIds` 也是字符串数组。
- 新建规则文档说明 `userChange.addIds` 和 `userChange.addNames` 可任选一个传,两者都传时取并集。
- `configs.functions.permission` 常用值:`1` 允许、`2` 允许并记录、`3` 限制并记录、`6` 限制。`password`、`mouse_click`、`keyboard_input` 只支持 `1`、`6`。
- `request_id` 会由 CLI 输出到 `meta.requestId`,排查接口问题时优先记录该值。
+131
View File
@@ -0,0 +1,131 @@
---
name: ziniao-account
version: 1.1.0
description: "紫鸟账号(店铺)管理:店铺账号的查询、创建、修改、删除、授权和标签管理。当用户需要查看店铺账号列表、创建/修改/删除账号、管理账号授权或标签时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 账号(店铺)管理
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)**
**CRITICAL — Shortcuts 执行前,务必先读取对应的 references/ 说明文档。**
紫鸟的"账号"指浏览器店铺账号(store),不是员工用户。本模块共覆盖 22 个接口,全部已有快捷命令。
> **注意区分:** `account` 命令通过**服务端 API** 管理店铺账号(创建、删除、授权、标签)。如需控制本地浏览器窗口(打开/关闭/导航/截图),请使用 `store` + `page` 命令(走本地 ZClaw Bridge)。
> **获取店铺列表的选择:**
> - 成员账号没有服务端 `account list` 权限,应使用 `store list`(本地 ZClaw Bridge)获取店铺列表。
> - Boss 账号如果目的是打开店铺浏览器,也应优先使用 `store list` 获取本地可用店铺,而非 `account list`。
> - `account list` 适用于需要管理店铺元数据(标签、授权、批量操作)的场景。
> - 成员类型可通过 `ziniao-cli config show` 结果中的 `isBoss` 字段判断。
## Shortcuts — 账号 CRUD
| 命令 | 说明 | 详细文档 |
|------|------|---------|
| `account list` | 查询账号列表 | [`references/ziniao-account-list.md`](references/ziniao-account-list.md) |
| `account create` | 创建账号 | [`references/ziniao-account-create.md`](references/ziniao-account-create.md) |
| `account update` | 编辑账号基础信息 | 只发修改字段:`--id xxx --name "新名"` |
| `account delete` | 删除账号(高风险) | `--id xxx`,逗号分隔可批量,需 `--yes` |
```bash
# 查询
ziniao-cli account list --name "某店" --format table
ziniao-cli account list --page-all
# 创建
ziniao-cli account create --name "我的店铺" --site-id 1 --remark "测试"
# 修改
ziniao-cli account update --id 123 --name "新名称" --site-id 1
# 删除(批量)
ziniao-cli account delete --id "101,102,103" --yes
```
## Shortcuts — 授权管理
| 命令 | 说明 |
|------|------|
| `account auth-add` | 授权员工访问店铺(`--store-id` 和 `--staff-id` 均支持逗号分隔批量) |
| `account auth-remove` | 撤销员工访问权限(`--store-id` 和 `--staff-id` 均支持逗号分隔批量) |
| `account auth-clean` | 清空店铺所有员工授权(高风险) |
| `account auth-list` | 查询某员工被授权的店铺列表 |
| `account auth-users` | 查询某店铺已授权的员工列表 |
```bash
# 批量授权(把 3 个店铺同时授权给 3 个员工)
ziniao-cli account auth-add --store-id "111,222,333" --staff-id "201,202,203"
# 撤销单人
ziniao-cli account auth-remove --store-id 111 --staff-id 201
# 查看某员工被授权的所有店铺
ziniao-cli account auth-list --user-id 201 --page-all --format table
# 清空授权(高风险)
ziniao-cli account auth-clean --store-id 111 --yes
```
## Shortcuts — 标签管理
| 命令 | 说明 |
|------|------|
| `account tag-list` | 查询企业下所有标签 |
| `account tag-of-store --store-id` | 查询某店铺绑定的标签列表 |
| `account tag-create --name` | 创建标签 |
| `account tag-rename --id --name` | 重命名标签 |
| `account tag-delete --id` | 删除标签(高风险) |
| `account tag-bind --tag-id --store-id` | 绑定标签到店铺(`--store-id` 支持批量) |
| `account tag-unbind --tag-id --store-id` | 解绑(`--store-id` 支持批量) |
| `account tag-replace --store-id --tag-ids` | 全量替换店铺的标签集合 |
| `account tag-clear --store-id` | 清空店铺所有标签(高风险) |
| `account tag-remove --tag-id --store-id` | 从指定店铺移除该标签(高风险,`--store-id` 支持批量) |
底层接口与请求体字段必须严格按接口文档发送,不要补发未记录的别名字段。
| 命令 | API | 请求体字段(不含公共 `companyId`) |
|------|-----|------------------------------------------|
| `account tag-list` | `/superbrowser/rest/v1/erp/tag/list` | 无 |
| `account tag-of-store --store-id` | `/superbrowser/rest/v1/store-tag/list` | `storeId`, 可选 `withSystem` |
| `account tag-create --name` | `/superbrowser/rest/v1/store-tag/add` | `tagName` |
| `account tag-rename --id --name` | `/superbrowser/rest/v1/store-tag/rename` | `tid`, `name` |
| `account tag-delete --id` | `/superbrowser/rest/v1/store-tag/delete` | `tagIds` |
| `account tag-bind --tag-id --store-id` | `/superbrowser/rest/v1/store-tag/bind` | `tagIds`, `storeIds` |
| `account tag-unbind --tag-id --store-id` | `/superbrowser/rest/v1/store-tag/unbind` | `tagIds`, `storeIds` |
| `account tag-replace --store-id --tag-ids` | `/superbrowser/rest/v1/store-tag/replace` | `tagIds`, `storeIds` |
| `account tag-clear --store-id` | `/superbrowser/rest/v1/store-tag/clear` | `storeIds` |
| `account tag-remove --tag-id --store-id` | `/superbrowser/rest/v1/store-tag/remove` | `tagIds`, `storeIds` |
```bash
# 查看所有标签
ziniao-cli account tag-list --format table
# 查看某店铺已有标签
ziniao-cli account tag-of-store --store-id 101
# 创建标签后批量绑定到多个店铺
ziniao-cli account tag-create --name "重点店铺"
ziniao-cli account tag-bind --tag-id 99 --store-id "101,102,103"
# 全量替换(将店铺 101 的标签换成 99 和 100)
ziniao-cli account tag-replace --store-id 101 --tag-ids "99,100"
# 从多个店铺移除某个标签
ziniao-cli account tag-remove --tag-id 99 --store-id "101,102" --yes
```
## 通用 api 覆盖(仅剩的接口)
```bash
# 缓存清理(仅此接口无快捷命令)
ziniao-cli api /superbrowser/rest/v1/erp/store/deletecookie --data '{"removeStoreId":"xxx","removeType":1}'
# removeType: 1=清除所有缓存, 2=仅保留 cookie, 3=仅保留二步验证状态
# 账号授权用户列表 / 用户有权限的账号列表
ziniao-cli api /superbrowser/rest/v1/erp/store/user/list --data '{"storeIdList":["xxx"],"page":1,"limit":10}'
ziniao-cli api /superbrowser/rest/v1/erp/user/stores --data '{"userId":"xxx","page":1,"limit":10}'
```
@@ -0,0 +1,43 @@
# account create
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
创建店铺账号。写入操作。CLI 将嵌套的 `storeData` 数组展平为命名 flag。
## 命令
```bash
ziniao-cli account create --name "新店铺"
ziniao-cli account create --name "US Store" --site-id 123 --proxy-id 456
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 是 | 账号名称 |
| `--site-id` | 否 | 平台 ID |
| `--proxy-id` | 否 | 代理设备 ID |
| `--group-id` | 否 | 分组 ID |
| `--remark` | 否 | 备注 |
## 底层 API(快捷命令不可用时的兜底)
```bash
ziniao-cli api /superbrowser/rest/v1/erp/store/create \
--data '{"storeData":[{"name":"新店铺","siteId":123}]}'
```
**注意事项:**
- `storeData` 必须是**数组**,即使只创建一个店铺
- 字段名是 `name`(不是 `storeName`)
- 在 **Windows Git Bash** 中,以 `/` 开头的路径会被自动转换为本地文件路径,需加环境变量:
```bash
MSYS_NO_PATHCONV=1 ziniao-cli api /superbrowser/rest/v1/erp/store/create \
--data '{"storeData":[{"name":"新店铺"}]}'
```
- 在 **PowerShell** 或 **CMD** 中无此问题
## 参考
- [ziniao-account](../SKILL.md) — 账号管理全部命令
@@ -0,0 +1,30 @@
# account list
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
查询账号列表。只读操作,支持分页和按名称搜索。
## 命令
```bash
ziniao-cli account list
ziniao-cli account list --format table
ziniao-cli account list --name "Rosehut"
ziniao-cli account list --page-all --page-size 50
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 否 | 按名称搜索 |
| `--page-size` | 否 | 每页条数(默认 20) |
| `--page-all` | 否 | 自动翻页获取全部 |
| `--page-limit` | 否 | 最大翻页数(默认 10,0 为不限) |
| `--page-delay` | 否 | 翻页间隔毫秒数(默认 200) |
| `--format` | 否 | json \| table \| csv |
| `--jq` | 否 | jq 过滤表达式 |
## 参考
- [ziniao-account](../SKILL.md) — 账号管理全部命令
@@ -0,0 +1,74 @@
---
name: ziniao-department
version: 1.0.0
description: "紫鸟部门管理:部门的查询、创建、修改、删除和排序。当用户需要查看组织架构、创建/修改/删除部门、调整部门排序时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 部门管理
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md),其中包含认证、错误处理和安全规则**
**CRITICAL — 所有 Shortcuts 在执行之前,务必先用 Read 工具读取其对应的 references/ 说明文档,禁止直接盲目调用命令。**
## 核心场景
### 查看组织架构
用户想了解公司部门结构时,使用 `--tree` 展示层级关系:
```bash
ziniao-cli department list --tree
```
输出树形结构:
```
├── 技术部 (id: 15868464646076)
│ ├── 前端组 (id: 15954943062431)
│ └── 后端组 (id: 15954943062432)
└── 市场部 (id: 15868464646077)
```
### 创建部门
`hierarchy` 由 CLI 自动推算(查父部门层级 +1),用户无需关心:
```bash
# 在已有部门下创建子部门(parentId 必须为已存在的部门 ID,不支持传 0)
ziniao-cli department create --name "华东区" --parent-id 15868464646076
```
> ⚠ `--parent-id` 不能传 0,必须传已存在的父部门 ID。如需查询可用的父部门 ID,先执行 `department list --tree`。
### 危险操作
删除部门是 `high-risk-write`,会要求确认。**删除父部门会级联删除所有子部门,且不可恢复。** 删除前建议先检查是否有子部门或员工:
```bash
ziniao-cli department list --tree # 检查子部门
ziniao-cli staff list --department-id xxx # 检查是否有员工
ziniao-cli department delete --id xxx # 执行删除(需确认)
```
## Shortcuts
| 命令 | 说明 | 详细文档 |
|------|------|---------|
| `department list` | 查询部门列表 | [`references/ziniao-department-list.md`](references/ziniao-department-list.md) |
| `department create` | 新增部门 | [`references/ziniao-department-create.md`](references/ziniao-department-create.md) |
| `department update` | 修改部门 | 只发修改字段:`--id xxx --name "新名"` |
| `department delete` | 删除部门(子部门及员工归属将被清除) | `high-risk-write`,`--yes` 跳过确认 |
| `department reorder` | 调整排序 | `--parent-id xxx --order "id1,id2,id3"` |
## 通用 api 覆盖
所有部门接口也可通过通用 api 命令调用:
```bash
ziniao-cli api /superbrowser/rest/v1/erp/department/list
ziniao-cli api /superbrowser/rest/v1/erp/department/add --data '{"name":"xxx","parentId":"<父部门ID>","hierarchy":"0"}'
ziniao-cli api /superbrowser/rest/v1/erp/department/update --data '{"id":"xxx","name":"新名"}'
ziniao-cli api /superbrowser/rest/v1/erp/department/delete --data '{"id":"xxx"}'
ziniao-cli api /superbrowser/rest/v1/erp/department/order --data '{"parentId":"xxx","order":["id1","id2"]}'
```
@@ -0,0 +1,31 @@
# department create
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md) 了解认证和安全规则。
新增部门。写入操作。
## 命令
```bash
# 创建顶级部门
ziniao-cli department create --name "市场部"
# 创建子部门(hierarchy 自动推算)
ziniao-cli department create --name "华东区" --parent-id 15868464646076
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 是 | 部门名称 |
| `--parent-id` | 否 | 父部门 ID(默认 "0" 表示顶级部门) |
## 智能推算
- `hierarchy` 由 CLI 自动推算:无 `--parent-id` 则 `hierarchy=0`;有则查父部门层级 +1
- 用户无需关心 `hierarchy` 参数
## 参考
- [ziniao-department](../SKILL.md) — 部门管理全部命令
@@ -0,0 +1,54 @@
# department list
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md) 了解认证和安全规则。
查询部门列表。只读操作。
## 命令
```bash
# 默认 JSON 输出
ziniao-cli department list
# 树形展示
ziniao-cli department list --tree
# 表格输出
ziniao-cli department list --format table
# jq 过滤顶级部门
ziniao-cli department list --jq '.[] | select(.parentId == 0)'
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--tree` | 否 | 以树形结构展示部门层级(按 parentId 关系渲染) |
| `--format` | 否 | 输出格式:json(默认)\| table \| csv |
| `--jq` / `-q` | 否 | jq 过滤表达式 |
## 输出字段
| 字段 | 说明 |
|------|------|
| `id` | 部门 ID |
| `name` | 部门名称 |
| `parentId` | 父部门 ID(0 表示顶级) |
| `hierarchy` | 层级深度 |
## 树形输出格式
当使用 `--tree` 时,按层级渲染:
```
├── 部门名 (id: 123)
│ ├── 子部门 (id: 456)
│ └── 子部门 (id: 789)
└── 其他部门 (id: 101)
```
## 参考
- [ziniao-department](../SKILL.md) — 部门管理全部命令
- [ziniao-shared](../../ziniao-shared/SKILL.md) — 认证和全局参数
+104
View File
@@ -0,0 +1,104 @@
---
name: ziniao-device
version: 1.1.0
description: "紫鸟设备管理:代理设备(IP)的查询、添加、修改、绑定/解绑、购买续费和自动续费。当用户需要查看代理设备列表、添加/修改自有代理、绑定设备到店铺或管理设备费用时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 设备管理
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)**
管理紫鸟浏览器使用的代理设备(IP)。常用 9 个接口已有快捷命令覆盖;价格查询、历史绑定记录使用通用 `api` 命令。
## 代理类型映射
| CLI 值 | API 值 | 说明 |
|--------|--------|------|
| http | 0 | HTTP 代理 |
| https | 1 | HTTPS 代理 |
| socks5 | 2 | SOCKS5 代理 |
| ssh | 3 | SSH 隧道 |
| ssl | 4 | SSL 代理 |
## Shortcuts — 查询与自有设备
| 命令 | 说明 | 详细文档 |
|------|------|---------|
| `device list` | 查询设备列表 | [`references/ziniao-device-list.md`](references/ziniao-device-list.md) |
| `device add-custom` | 添加自有设备 | [`references/ziniao-device-add-custom.md`](references/ziniao-device-add-custom.md) |
| `device update-custom` | 修改自有设备 | 部分更新:`--id xxx --addr "1.2.3.4"` |
```bash
ziniao-cli device list --format table
ziniao-cli device list --page-all
ziniao-cli device add-custom --name "我的代理" --proxy-type socks5 \
--addr "1.2.3.4" --port 1080 --proxy-user admin --proxy-pass secret
ziniao-cli device update-custom --id 123 --addr "5.6.7.8"
```
底层接口:设备列表 `/superbrowser/rest/v1/erp/ip/page`;添加自有设备 `/superbrowser/rest/v1/erp/ip/self/add/new`;修改自有设备 `/superbrowser/rest/v1/erp/ip/self/edit/new`。
## Shortcuts — 绑定管理
| 命令 | 说明 |
|------|------|
| `device bind --id --store-id` | 将设备绑定到店铺(`--store-id` 支持批量) |
| `device unbind --store-id` | 按店铺 ID 解绑设备(`--store-id` 支持批量) |
```bash
# 将设备 10 绑定到三个店铺
ziniao-cli device bind --id 10 --store-id "101,102,103"
# 批量解绑
ziniao-cli device unbind --store-id "101,102,103"
```
> ⚠ 批量解绑为逐个执行,部分失败不影响已成功项,也不会回滚。
底层接口:绑定 `/superbrowser/rest/v1/erp/ip/bind`;解绑 `/superbrowser/rest/v1/erp/ip/unbind`。
## Shortcuts — 费用相关(涉及真实扣费)
> ⚠ `purchase` 和 `renew` 为 `high-risk-write`,执行前会要求确认或需传 `--yes`。
| 命令 | 说明 |
|------|------|
| `device plan-list` | 查询可购买的套餐列表(先看这里) |
| `device purchase --plan-id --period-id --count` | 购买套餐(涉及费用,不可撤销) |
| `device renew --id --period-id` | 续费设备(`--id` 支持批量,涉及费用) |
| `device auto-renew --id --enable` | 批量开启/关闭自动续费 |
```bash
# 先查套餐,再购买
ziniao-cli device plan-list --format table
ziniao-cli device purchase --plan-id "plan_xxx" --period-id 1 --count 3
# 续费单台
ziniao-cli device renew --id 10 --period-id 1
# 批量开启自动续费
ziniao-cli device auto-renew --id "10,11,12" --enable true
# 关闭自动续费(bool 类型必须用等号连接)
ziniao-cli device auto-renew --id "10,11,12" --enable=false
# 关闭自动续费
ziniao-cli device auto-renew --id 10 --enable=false
```
底层接口:套餐 `/superbrowser/rest/v1/erp/package/new`;购买 `/superbrowser/rest/v1/erp/purchase`;续费 `/superbrowser/rest/v1/erp/renew`;自动续费 `/superbrowser/rest/v1/erp/ip/renewal`。
## 通用 api 覆盖
```bash
# 查询已购设备价格
ziniao-cli api /superbrowser/rest/v1/erp/proxy/purchased_package_info --data '{"proxyId":"123"}'
# 查询设备历史绑定记录
ziniao-cli api /superbrowser/rest/v1/erp/ip/historybind --data '{"proxyId":"123"}'
```
@@ -0,0 +1,52 @@
# device add-custom
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
添加自有代理设备。CLI 将嵌套的 `proxy` 对象展平为命名 flag。
底层 API:`/superbrowser/rest/v1/erp/ip/self/add/new`
## 命令
```bash
# 基础用法
ziniao-cli device add-custom \
--name "美国代理1" \
--proxy-type socks5 \
--addr "1.2.3.4" \
--port 1080
# 带认证
ziniao-cli device add-custom \
--name "美国代理1" \
--proxy-type socks5 \
--addr "1.2.3.4" \
--port 1080 \
--proxy-user admin \
--proxy-pass secret
# 动态 IP
ziniao-cli device add-custom \
--name "动态代理" \
--proxy-type http \
--addr "proxy.example.com" \
--port 8080 \
--dynamic
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 是 | 设备名称 |
| `--proxy-type` | 是 | http \| https \| socks5 \| ssh \| ssl |
| `--addr` | 是 | 代理地址 |
| `--port` | 是 | 代理端口 |
| `--proxy-user` | 否 | 代理用户名 |
| `--proxy-pass` | 否 | 代理密码 |
| `--dynamic` | 否 | 是否动态 IP |
| `--defy-warning` | 否 | 是否无视风险导入:1=是,0=否,默认 1 |
## 参考
- [ziniao-device](../SKILL.md) — 设备管理全部命令
@@ -0,0 +1,29 @@
# device list
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
查询设备列表。只读操作。
底层 API:`/superbrowser/rest/v1/erp/ip/page`
## 命令
```bash
ziniao-cli device list
ziniao-cli device list --format table
ziniao-cli device list --page-all
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--page-size` | 否 | 每页条数(默认 20) |
| `--page-all` | 否 | 自动翻页获取全部 |
| `--page-limit` | 否 | 最大翻页数(默认 10,0 为不限) |
| `--page-delay` | 否 | 翻页间隔毫秒数(默认 200) |
| `--format` | 否 | json \| table \| csv |
## 参考
- [ziniao-device](../SKILL.md) — 设备管理全部命令
@@ -0,0 +1,137 @@
---
name: ziniao-openapi-explorer
version: 1.1.0
description: "紫鸟 API 探索:当现有快捷命令无法满足需求时,通过通用 api 命令和已知路径清单探索紫鸟全部 73 个 API 接口。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# API 探索
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)。
当用户的需求**无法被现有快捷命令覆盖**时,使用本技能查找对应的 API 路径并通过 `ziniao-cli api` 裸调。
## 探索流程
### Step 1: 确认现有命令不足
```bash
ziniao-cli department --help
ziniao-cli staff --help
ziniao-cli account --help
ziniao-cli device --help
ziniao-cli role --help
```
如果已有对应快捷命令,直接使用。
### Step 2: 从路径清单定位 API
**禁止猜测 API 路径**,必须从以下清单中查找。
#### 部门(5 个)✅ 全部已有快捷命令
| 操作 | 路径 | 快捷命令 |
|------|------|---------|
| 列表 | `/superbrowser/rest/v1/erp/department/list` | `department list` |
| 新增 | `/superbrowser/rest/v1/erp/department/add` | `department create` |
| 修改 | `/superbrowser/rest/v1/erp/department/update` | `department update` |
| 删除 | `/superbrowser/rest/v1/erp/department/delete` | `department delete` |
| 排序 | `/superbrowser/rest/v1/erp/department/order` | `department reorder` |
#### 员工(5 个)✅ 全部已有快捷命令
| 操作 | 路径 | 快捷命令 |
|------|------|---------|
| 列表 | `/superbrowser/rest/v1/erp/staff/list` | `staff list` |
| 新增 | `/superbrowser/rest/v2/erp/staff/add` | `staff create` |
| 修改 | `/superbrowser/rest/v2/erp/staff/modify` | `staff update` |
| 状态 | `/superbrowser/rest/v1/erp/staff/status` | `staff enable/disable/remove` |
| 调岗 | `/superbrowser/rest/v1/erp/staff/department` | `staff transfer` |
#### 账号(23 个)— 仍有 4 个需用 `api`
| 分类 | 路径前缀 | 快捷命令 |
|------|---------|---------|
| CRUD | `/superbrowser/rest/v1/erp/store/list\|create\|update/base\|delete` | `account list/create/update/delete` |
| 缓存 | `/superbrowser/rest/v1/erp/store/deletecookie` | `api` |
| 授权 | `/superbrowser/rest/v1/erp/store/auth/add\|delete\|clean`、`/superbrowser/rest/v1/erp/user/stores`、`/superbrowser/rest/v1/erp/store/user/list` | `account auth-*` |
| 授权查询 | `/superbrowser/rest/v1/erp/store/auth/query` | `api` |
| 用户账号 | `/superbrowser/rest/v1/erp/store/list/by/user` | `api` |
| 附加信息 | `/superbrowser/rest/v1/erp/store/addtion` | `api` |
| 标签 | `/superbrowser/rest/v1/erp/tag/list`、`/superbrowser/rest/v1/store-tag/add\|delete\|rename\|bind\|unbind\|replace\|clear\|remove\|list` | `account tag-*` |
#### 设备(11 个)— 仍有 2 个需用 `api`
| 分类 | 路径前缀 | 快捷命令 |
|------|---------|---------|
| 查询 | `/superbrowser/rest/v1/erp/ip/page`、`/superbrowser/rest/v1/erp/proxy/purchased_package_info`、`/superbrowser/rest/v1/erp/ip/historybind` | `device list`(price/history 用 `api`)|
| 套餐 | `/superbrowser/rest/v1/erp/package/new` | `device plan-list` |
| 购买 | `/superbrowser/rest/v1/erp/purchase\|renew` | `device purchase/renew` |
| 自有 | `/superbrowser/rest/v1/erp/ip/self/add/new\|edit/new` | `device add-custom/update-custom` |
| 绑定 | `/superbrowser/rest/v1/erp/ip/bind\|unbind\|renewal` | `device bind/unbind/auto-renew` |
#### 角色(7 个)✅ 全部已有快捷命令
| 操作 | 路径 | 快捷命令 |
|------|------|---------|
| 列表 | `/superbrowser/rest/v1/erp/per/role/list` | `role list` |
| 详情 | `/superbrowser/rest/v1/erp/per/role/detail` | `role detail` |
| 新增 | `/superbrowser/rest/v1/erp/per/role/add` | `role create` |
| 修改 | `/superbrowser/rest/v1/erp/per/role/edit` | `role update` |
| 分配 | `/superbrowser/rest/v1/erp/staff/change/role` | `role assign` |
| 用户角色 | `/superbrowser/rest/v1/erp/staff/role/list` | `role staff-roles` |
| 权限列表 | `/superbrowser/rest/v1/erp/per/permission/list` | `role permissions` |
#### 访问策略(22 个)— 全部需用 `api`
| 分类 | 路径 |
|------|------|
| 规则 | `/superbrowser/rest/v1/erp/security/access_rule/list` |
| 规则 | `/superbrowser/rest/v1/erp/security/access_rule/detail` |
| 规则 | `/superbrowser/rest/v1/erp/security/access_rule/add` |
| 规则 | `/superbrowser/rest/v1/erp/security/access_rule/edit` |
| 规则 | `/superbrowser/rest/v1/erp/security/access_rule/delete` |
| 规则 | `/superbrowser/rest/v1/erp/security/access_rule/active` |
| 规则成员 | `/superbrowser/rest/v1/erp/security/access_rule/change_user` |
| 规则账号 | `/superbrowser/rest/v1/erp/security/access_rule/change_account` |
| 规则账号 | `/superbrowser/rest/v1/erp/security/access_rule/account/bindable_list` |
| 网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/list` |
| 网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/detail` |
| 网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/add` |
| 网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/edit` |
| 网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/delete` |
| 网页 | `/superbrowser/rest/v1/erp/security/access_rule_url/change_group` |
| 网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/list` |
| 网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/add` |
| 网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/edit` |
| 网页分组 | `/superbrowser/rest/v1/erp/security/access_rule_url_group/delete` |
| 账号策略 | `/superbrowser/rest/v1/erp/security/access_rule/list/one_account` |
| 账号策略 | `/superbrowser/rest/v1/erp/security/access_rule/account_ref/add_to_account` |
| 账号策略 | `/superbrowser/rest/v1/erp/security/access_rule/account_ref/remove_from_account` |
### Step 3: 使用 dry-run 探索
```bash
# 先预览请求
ziniao-cli api /superbrowser/rest/v1/erp/per/role/list --dry-run
# 确认后执行
ziniao-cli api /superbrowser/rest/v1/erp/per/role/list
```
## 安全规则
- **禁止猜测** API 路径或参数,必须从清单中查找
- 写入/删除类操作前确认用户意图
- 建议先用 `--dry-run` 预览
## 参考
- [ziniao-shared](../ziniao-shared/SKILL.md) — 认证和全局参数
- [ziniao-account](../ziniao-account/SKILL.md) — 账号/授权/标签快捷命令
- [ziniao-device](../ziniao-device/SKILL.md) — 设备管理快捷命令
- [ziniao-role](../ziniao-role/SKILL.md) — 角色管理快捷命令
- [ziniao-skill-maker](../ziniao-skill-maker/SKILL.md) — 如需将常用 API 固化为新 Skill
+54
View File
@@ -0,0 +1,54 @@
---
name: ziniao-page
description: "Use when a request concerns a webpage running inside an already-opened 紫鸟/ZClaw store browser, including navigation, inspection, interaction, extraction, or screenshots. If the task starts by identifying or opening a store, use ziniao-store first."
metadata:
requires:
bins: ["ziniao-cli"]
---
# 紫鸟页面能力
本技能负责已打开紫鸟店铺浏览器中的通用网页资源操作,不负责店铺生命周期,也不定义用户的业务流程。
## 负责范围
- 对当前店铺浏览器中的网页进行通用操作或读取。
- 在页面操作中复用已确认的 `store-id`,必要时指定 `target-id`。
- 根据页面状态选择 CLI 已提供的页面命令;参数以 `ziniao-cli page <command> --help` 为准。
- 页面操作需要复杂编排时,按需读取 automation reference。
## 路由边界
- 已有店铺浏览器并且目标是网页资源:直接使用本技能。
- 任务尚未确定或打开店铺:转到 `ziniao-store`,不要自行猜测店铺 ID。
- 不要把用户的业务目标或任务参数写入技能;这些是用户任务,由模型组合通用页面能力完成。
- 配置、认证、Bridge、客户端或网络异常:按需读取 `ziniao-shared` 的诊断规则,不在本技能重复维护环境修复流程。
## 最小执行规则
1. 确认店铺已打开,并取得有效 `store-id`;页面命令通常都需要它。
2. 先确认当前页面状态,再执行必要的页面操作;页面变化后不要复用失效的目标或定位信息。
3. 对导航、等待、读取、交互、提取和截图等能力,直接使用 CLI 的对应命令;不确定参数时先看 `--help`。
4. 涉及附件或图片上传时,在下载、路径转换或上传调用前,必须完整读取 [page upload 安全规则](references/ziniao-page-upload.md);未读取不得执行上传。
5. 只在当前任务需要时读取 reference,不要默认读取全部页面文档。
6. 报错时区分页面/目标定位问题和环境问题;环境问题再转 `ziniao-shared`/`doctor`。
## 页面读取与定位顺序
1. 页面状态、`target-id` 和稳定 CSS selector 已知时,直接复用并执行必要操作;不要为每一步重复读取页面。
2. 需要理解页面或验证导航、搜索、提交结果时,优先使用 `page content --content-format structured`;只需 URL/title 时也可使用 `page exec`。
3. selector 未知时,先使用 `page content`、`page query` 或 `page exec` 分析 DOM,再使用 `page input` / `page click --selector`。
4. 只有上传控件、自定义或图标控件、多个相似控件存在歧义,或上述 DOM 方法仍无法唯一定位时,才使用 `page snapshot` 和当前页面 ref。
`page snapshot` 不是通用页面状态检查命令。不得为了验证店铺窗口、读取 URL/title/`target-id`、操作已知 selector、普通搜索或结果验证而调用 snapshot;页面未变化时也不得重复创建 snapshot/ref。snapshot ref 只属于当前店铺、tab 和页面,页面导航或刷新后不得复用。
输入后提交只能选择一种方式:使用 `page input --submit` 后等待并验证结果,或不传 `--submit` 再执行提交按钮的 `page click --wait-nav`;不得重复提交。
## 按需参考
- [page visit](references/ziniao-page-visit.md):需要导航时读取。
- [page screenshot](references/ziniao-page-screenshot.md):需要截图时读取。
- [page upload 安全规则](references/ziniao-page-upload.md):涉及附件、图片、文件输入框或 `page upload` 时必须读取。
- [automation run](references/ziniao-page-automation.md):需要多步骤自动化时读取。
- `ziniao-store`:需要确定或打开店铺时使用。
- `ziniao-shared`:仅在环境、配置、认证或 Bridge 诊断时读取。
@@ -0,0 +1,132 @@
# automation run
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。紫鸟浏览器必须已启动,且店铺已打开。
执行多步骤自动化流程。将多个页面操作编排为一条命令执行,减少多次调用的开销。
## 命令
```bash
ziniao-cli automation run --steps '[
{"type": "visit", "url": "https://example.com", "waitUntil": "networkidle"},
{"type": "wait", "selector": "#login"},
{"type": "click", "selector": "#login"},
{"type": "input", "selector": "#username", "text": "admin", "clear": true},
{"type": "input", "selector": "#password", "text": "pass123", "clear": true},
{"type": "click", "selector": "#submit", "waitForNavigation": true},
{"type": "screenshot", "fullPage": true, "path": "./result.png"}
]'
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--steps` | 是 | 步骤 JSON 数组 |
## 步骤类型及字段
### visit — 导航到 URL
| 字段 | 必填 | 说明 |
|------|------|------|
| `url` | 是 | 目标 URL |
| `waitUntil` | 否 | 等待条件: domcontentloaded / load / networkidle |
| `timeoutMs` | 否 | 超时毫秒数 |
### click — 点击元素
| 字段 | 必填 | 说明 |
|------|------|------|
| `selector` | 是 | CSS 选择器 |
| `waitForNavigation` | 否 | 点击后等待导航完成 |
| `timeoutMs` | 否 | 超时毫秒数 |
### input — 输入文本
| 字段 | 必填 | 说明 |
|------|------|------|
| `selector` | 是 | CSS 选择器 |
| `text` | 是 | 要输入的文本 |
| `clear` | 否 | 输入前清空已有内容 |
| `submit` | 否 | 输入后提交表单 |
| `timeoutMs` | 否 | 超时毫秒数 |
### scroll — 滚动页面
| 字段 | 必填 | 说明 |
|------|------|------|
| `x` | 否 | 水平滚动像素 |
| `y` | 否 | 垂直滚动像素 |
| `selector` | 否 | 滚动目标元素选择器 |
| `behavior` | 否 | 滚动行为: auto / smooth |
### screenshot — 截图
| 字段 | 必填 | 说明 |
|------|------|------|
| `fullPage` | 否 | 全页截图 |
| `path` | 否 | 保存路径 |
### wait — 等待
| 字段 | 必填 | 说明 |
|------|------|------|
| `selector` | 否* | 等待元素出现(与 timeout 二选一) |
| `timeout` | 否* | 延时毫秒数(与 selector 二选一) |
| `timeoutMs` | 否 | 等待超时上限 |
### exec — 执行 JavaScript
| 字段 | 必填 | 说明 |
|------|------|------|
| `script` | 是 | JavaScript 代码 |
| `timeoutMs` | 否 | 超时毫秒数 |
## 使用示例
### 登录流程
```bash
ziniao-cli automation run --steps '[
{"type": "visit", "url": "https://sellercentral.amazon.com", "waitUntil": "networkidle"},
{"type": "input", "selector": "#ap_email", "text": "seller@example.com", "clear": true},
{"type": "input", "selector": "#ap_password", "text": "password123", "clear": true},
{"type": "click", "selector": "#signInSubmit", "waitForNavigation": true},
{"type": "screenshot", "fullPage": true}
]'
```
### 滚动加载 + 截图
```bash
ziniao-cli automation run --steps '[
{"type": "visit", "url": "https://www.amazon.com/dp/B0XXXXX", "waitUntil": "networkidle"},
{"type": "scroll", "y": 500, "behavior": "smooth"},
{"type": "wait", "timeout": 1000},
{"type": "scroll", "y": 1000, "behavior": "smooth"},
{"type": "wait", "timeout": 1000},
{"type": "screenshot", "fullPage": true, "path": "./listing-full.png"}
]'
```
### 提取数据后截图确认
```bash
ziniao-cli automation run --steps '[
{"type": "visit", "url": "https://example.com/dashboard", "waitUntil": "networkidle"},
{"type": "wait", "selector": ".data-table"},
{"type": "exec", "script": "document.querySelector('.data-table').style.border = '2px solid red'"},
{"type": "screenshot", "path": "./dashboard.png"}
]'
```
## 注意事项
- steps 必须为合法 JSON 数组,建议用单引号包裹避免转义问题
- 步骤按顺序执行,任何一步失败会终止后续步骤
- 复杂的条件逻辑(if/else)不适合用 automation run,应拆分为多条单独命令由 Agent 控制
## 参考
- [ziniao-page](../SKILL.md) — 页面操作全部命令
@@ -0,0 +1,35 @@
# page screenshot
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。店铺必须已打开。
对当前页面截图。
## 命令
```bash
# 视口截图
ziniao-cli page screenshot --store-id abc123
# 全页截图
ziniao-cli page screenshot --store-id abc123 --full-page
# 保存到指定路径
ziniao-cli page screenshot --store-id abc123 --path "./output.png"
# 指定超时
ziniao-cli page screenshot --store-id abc123 --timeout 10000
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--store-id` | 是 | 店铺 ID |
| `--full-page` | 否 | 全页截图(默认仅视口) |
| `--path` | 否 | 保存路径 |
| `--timeout` | 否 | 超时毫秒数 |
| `--target-id` | 否 | 目标页面 ID(多 tab 时指定) |
## 参考
- [ziniao-page](../SKILL.md) — 页面操作全部命令
@@ -0,0 +1,55 @@
# page upload 安全规则
页面附件或图片上传属于写操作。执行任何下载、路径转换、文件选择或上传调用前,先完成来源、页面目标和用户确认检查。
## 来源边界
- `page upload` 只接受用户提供的本地文件路径。
- 若附件来源是 `http://` 或 `https://` URL,立即停止上传并原样回复:
> 暂不支持从 URL 上传附件,请提供本地文件路径。
- 不得先下载、复制、缓存或转换 URL 内容,再把生成的本地文件当作可信来源上传。用户要求跳过该限制也不能改变来源边界。
- 不得把导航 URL、页面链接或下载链接解释为附件路径。
## 页面与控件定位
1. 上传前确认当前 `store-id`、当前页面和唯一 `target-id`。多个可用标签页而用户未指定目标时,停止并让用户确认,不得任意选择。
2. 只使用当前页面快照产生的 ref。页面 reload、URL 变化、标签页切换或 ref 失效后,重新执行 `page snapshot`。
3. 上传控件不唯一时,根据当前快照的可访问名称和上下文,通过 `--trigger-ref` 或 `--hint` 消歧;不要先单独点击上传入口,也不要猜测泛选择器。
错误处理:
- `PAGE_TARGET_AMBIGUOUS`:重新获取当前页面快照并确定唯一 `target-id`;不得任意选择其他标签页。
- `PAGE_TARGET_NOT_FOUND`:原目标已失效;重新获取当前店铺快照,不得回退到其他标签页。
- `UPLOAD_CONTROL_NOT_DISCOVERED`:重新快照检查动态上传入口;仍未发现时停止并报告。
- `UPLOAD_CONTROL_AMBIGUOUS`:使用当前快照的 `--trigger-ref` 或 `--hint` 消歧;仍无法唯一确认时停止并让用户选择。
## 外部本地路径确认
当下载目录外的本地路径返回 `confirmationRequired` 时:
本轮最终用户可见回复必须是一个不可拆分的确认包,并按以下顺序同时包含:
1. 原样输出完整 `confirmationMessage`,不得摘要、改写或删除字段。
2. 紧接着实际显示 `screenshotFilePath` 指向的截图。
3. 明确说明文件尚未上传。
4. 最后等待用户按照 `confirmationMessage` 的要求明确确认。
仅调用图片读取工具、仅在中间进度消息或折叠区域显示图片、或只输出截图路径,均不算已经向用户展示截图。结束本轮前必须确认最终回复中同时可见完整确认文字和实际截图。
截图无法读取或无法在最终回复中展示时,本轮不得索要确认,也不得重试上传。用户明确确认后,只以相同的 `store-id`、`target-id`、文件来源、目标控件和上传意图,追加原响应的 `--confirmation-token <token>` 重试一次。不得换标签页、换文件、换控件或改写来源。先前的笼统授权或“无需再确认”不能替代本次确认。
页面、店铺会话或任一上传参数发生变化时,原 token 不再用于新的上传意图;重新开始定位和确认流程。
## 结果判定与重试
上传命令的结果只描述文件控件状态,不能单独证明网站业务提交完成:
- `confirmationRequired=true` 或 `stage=confirmation_pending`:尚未获得上传授权;不得声称已上传。
- `attached=true, stage=input_attached`:文件仍在网页文件输入控件中;继续根据页面可见文件名、缩略图、进度或错误提示验证。
- `attached=true, stage=input_consumed`:网页组件已接收并消费或清空输入;仍需等待页面可见的处理结果。
- `attached=false, stage=input_unverified`:CDP 已执行但页面是否接收无法确认;不得报告成功或失败,也不得自动重试。先通过新快照、截图、文件名、进度或错误提示确认;只有页面明确失败,或把本次不确定结果告知用户后用户再次明确要求时才重试。任务开始前的笼统授权不构成这次重试授权。
- `UPLOAD_ATTACH_VERIFY_FAILED`:重新快照并检查页面可见状态;不得盲目重试或声称成功。
只有页面出现与目标文件对应的可见业务结果,才能报告网站侧上传完成。文件进入控件、命令退出码为 0、`attached=true` 或 `input_consumed` 都不是业务完成证明。
@@ -0,0 +1,37 @@
# page visit
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。店铺必须已打开。
导航到指定 URL。
## 命令
```bash
ziniao-cli page visit --store-id abc123 --url "https://www.amazon.com"
# 等待 networkidle 后再返回
ziniao-cli page visit --store-id abc123 --url "https://www.amazon.com" --wait-until networkidle
# 指定超时
ziniao-cli page visit --store-id abc123 --url "https://www.amazon.com" --timeout 60000
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--store-id` | 是 | 店铺 ID |
| `--url` | 是 | 目标 URL |
| `--wait-until` | 否 | 等待条件: domcontentloaded \| load \| networkidle |
| `--timeout` | 否 | 超时毫秒数 |
| `--target-id` | 否 | 目标页面 ID(多 tab 时指定) |
## 提示
- 导航后建议用 `page wait-nav` 等待页面加载完成
- 可用 `page content` 获取页面内容确认是否到达目标页面
- `--wait-until networkidle` 适合 SPA 页面,等待所有网络请求完成
## 参考
- [ziniao-page](../SKILL.md) — 页面操作全部命令
+94
View File
@@ -0,0 +1,94 @@
---
name: ziniao-role
version: 1.0.0
description: "紫鸟角色管理:角色的查询、创建、修改、分配和权限查看。当用户需要查看角色列表、创建/修改角色、将角色分配给员工或查看角色成员时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 角色管理
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)**
管理紫鸟 ERP 中的角色(权限组)。角色决定员工能看到哪些功能、能操作哪些店铺。
创建员工时需要传入 `--role-id`,可通过 `role list` 获取。
## Shortcuts
| 命令 | 说明 |
|------|------|
| `role list` | 查询所有角色 |
| `role detail --id` | 查看角色详情(含权限列表) |
| `role create --name --identity-id --permission-ids` | 创建角色 |
| `role update --id --name --identity-id --permission-ids` | 修改角色(需全量提交,不传的字段可能被清空) |
| `role assign --role-id --staff-id` | 将角色分配给员工(`--staff-id` 支持批量) |
| `role staff-roles --staff-id` | 查看某员工已分配的角色列表 |
| `role permissions --identity-id` | 查看指定身份的可用权限项 |
## 核心场景
### 查看角色与权限
```bash
# 查询所有角色(先获取 role-id)
ziniao-cli role list --format table
# 查看某角色详情
ziniao-cli role detail --id 123
# 查看系统支持的全部权限项(创建角色前先查)
ziniao-cli role permissions --identity-id 2
```
### 创建与修改角色
```bash
# 创建角色(权限使用权限 ID,多个权限用逗号分隔)
ziniao-cli role create --name "店铺运营" --identity-id 2 --permission-ids "100,101" --desc "负责日常店铺操作"
# 修改角色(需全量提交,建议先 role detail 查看当前值)
ziniao-cli role update --id 123 --name "高级运营" --identity-id 2 --permission-ids "100,101,102"
```
### 分配角色给员工
```bash
# 将角色分配给单个员工
ziniao-cli role assign --role-id 123 --staff-id 456
# 批量分配给多人
ziniao-cli role assign --role-id 123 --staff-id "456,789,101"
# 查看某员工已分配的角色
ziniao-cli role staff-roles --staff-id 456 --format table
```
### 典型工作流:新建角色并批量分配
```bash
# 1. 查看系统已有角色,避免重复创建
ziniao-cli role list --format table
# 2. 查看普通成员可用权限项
ziniao-cli role permissions --identity-id 2 --jq '.[] | .id'
# 3. 创建新角色
ziniao-cli role create --name "跨境运营" --identity-id 2 --permission-ids "100,101,102"
# 4. 获取新角色 ID(从上一步输出的 data.id 取)
# 5. 批量分配给员工
ziniao-cli role assign --role-id <new-id> --staff-id "201,202,203"
```
## 通用 api 覆盖
```bash
ziniao-cli api /superbrowser/rest/v1/erp/per/role/list
ziniao-cli api /superbrowser/rest/v1/erp/per/role/detail --data '{"roleId":"xxx"}'
ziniao-cli api /superbrowser/rest/v1/erp/per/role/add --data '{"roleName":"xxx","identityId":2,"permissionIds":[100,101]}'
ziniao-cli api /superbrowser/rest/v1/erp/per/role/edit --data '{"roleId":"xxx","roleName":"yyy","identityId":2,"permissionIds":[100,101]}'
ziniao-cli api /superbrowser/rest/v1/erp/staff/change/role --data '{"roleId":"xxx","staffIds":["yyy"]}'
ziniao-cli api /superbrowser/rest/v1/erp/staff/role/list --data '{"staffId":"yyy"}'
ziniao-cli api /superbrowser/rest/v1/erp/per/permission/list --data '{"identityId":2}'
```
+401
View File
@@ -0,0 +1,401 @@
---
name: ziniao-shared
version: 1.0.0
description: "紫鸟 CLI 共享基础:应用配置初始化、ZClaw 客户端与 Bridge 就绪、统一 apiKey 认证、错误处理、输出格式和安全规则。当用户需要首次使用 ziniao-cli、配置 API Key、启动紫鸟客户端、登录 ZClaw 或排查认证/Bridge 问题时触发。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# ziniao-cli 共享规则
本技能指导你如何通过 ziniao-cli 操作紫鸟开放平台资源和控制紫鸟浏览器,以及有哪些注意事项。
## 配置初始化
首次使用需运行 `ziniao-cli config init` 完成应用配置。
### 初始化模式
| 命令 | 场景 | 行为 |
|------|------|------|
| `ziniao-cli config init --new` | **AI Agent(推荐)** | 直接进入新建应用流程,输出浏览器链接后轮询等待 |
| `ziniao-cli config init` | 人类用户交互 | 菜单选择:[1] 新建应用 [2] 手动输入 Key |
| `ziniao-cli config init --api-key-stdin` | CI/CD 管道 | 从 stdin 读取已有 API Key |
| `ziniao-cli config init --api-key-stdin --member` | 成员账号 CI | 跳过企业信息获取,仅用于控制浏览器 |
| `ziniao-cli config init --profile <name>` | 多账号 | 指定 profile 名称(不传则自动命名) |
### AI Agent 初始化流程
使用 background 方式执行以下命令,启动后读取 stderr 输出,从中提取浏览器链接并展示给用户:
```bash
# 直接进入新建应用流程(该命令会阻塞直到审核通过、被拒绝或超时 1 小时)
ziniao-cli config init --new
```
输出示例(Boss 账号):
```
请在浏览器中打开以下链接完成应用创建:
https://open.ziniao.com/memberAuth?cliRequestId=a1b2c3d4-...&from=cli
⏳ 等待应用创建及审核... (按 Ctrl+C 取消)
⏳ 等待中... 已等待 30s
✓ 审核已通过
⏳ 正在获取企业信息...
✓ 企业 ID: 15393571083459
✓ 配置已保存
```
输出示例(成员账号):
```
...
✓ 审核已通过
✓ 成员账号,跳过企业信息获取
✓ 配置已保存
```
Agent 应该:
1. 后台执行 `config init --new`
2. 从输出中提取 URL(包含 `memberAuth?cliRequestId=` 的行)
3. 将链接展示给用户,提示在浏览器中打开完成应用创建
4. 等待命令完成(审核通过/拒绝/超时)
5. 如果审核被拒绝,告知用户联系 Boss 审批
apiKey 加密后写入本地。沙箱/容器可直接使用,无需系统钥匙串。
### Boss 与成员账号
初始化时服务端返回 `isBoss` 标识,决定账号权限范围:
| 账号类型 | 服务端 API | ZClaw Bridge(本地浏览器) |
|---------|-----------|--------------------------|
| Boss | ✓ 全部可用 | ✓ 全部可用 |
| 成员 | ✗ 不可用(返回 auth 错误) | ✓ 全部可用 |
服务端 API 权限仅 Boss 账号可申请和调用。成员账号不能开通这些权限点,也不存储 companyId;所有通过 `api` 命令或服务端快捷命令(account/staff/department/role/device)的请求会被拦截并提示"需要 Boss 权限"。
### 检查配置
```bash
ziniao-cli config show # 查看当前配置(含 profile 名称)
ziniao-cli config list # 列出所有 profile
ziniao-cli doctor # 全面自检(配置 + apiKey + 网络 + ZClaw Bridge)
```
## ZClaw 客户端与 Bridge 就绪
ZClaw 本地浏览器操作依赖紫鸟客户端和本地 Bridge。本次任务首次执行店铺或页面命令前,先运行以下命令检查环境:
### 登录命令硬约束
本节所有“登录”“自动登录”“先登录”都只表示逐字执行 `ziniao-cli zclaw login`。不存在可替代的 `ziniao-cli login`,也不得省略 `zclaw`。在输出或执行登录步骤前先核对完整命令。
```bash
ziniao-cli doctor
```
如果 `doctor` 各项明细确认配置、API Key、服务端连通性、Bridge 连通且客户端账号与当前 profile 一致,直接执行原店铺或页面命令;不要只依据最后的汇总文字。一次任务内已经检查通过且认证、profile 和客户端状态未变化时,不要为每个后续命令重复检查。
当 `doctor` 的客户端明细明确显示“紫鸟客户端已启动,但尚未登录”或 `[CLIENT_LOGGED_OUT]`,且 Bridge 已连通、没有 API Key、终端绑定或账号不一致错误时,这已经是确定的未登录状态。未登录恢复命令固定且只能是 `ziniao-cli zclaw login`;`ziniao-cli login` 不是有效命令,不得缩写或替换。必须在任何 `store`、`page` 业务命令之前按以下顺序执行:
```bash
ziniao-cli zclaw login
ziniao-cli doctor
# 仅当 doctor 明细全部通过时,执行一次原业务命令
```
禁止先调用 `store list`、`store resolve`、`store open` 或 `page` 命令来再次探测登录状态,即使 `doctor` 退出码为 0 或 Bridge 显示正常也不例外。登录失败或复查仍未通过时立即停止并报告实际错误。
仅当 `doctor` 的客户端检查明确显示“紫鸟客户端未启动或本地服务未就绪”时,才可尝试启动客户端。调用前按**当前 Shell**选择一种命令检查 `ziniao` 是否可解析,不得把某一种检测命令用于所有平台,也不得依次执行多种检测命令:
| 当前 Shell | 命令检测 |
|------------|----------|
| Windows PowerShell | `Get-Command ziniao -ErrorAction SilentlyContinue` |
| Windows CMD | `where.exe ziniao` |
| macOS/Linux Bash、Zsh、sh,或 Windows Git Bash | `command -v ziniao` |
若当前 Shell 找不到 `ziniao`,不得调用或重试 `ziniao start`。macOS 例外:如果 `/Applications/ziniao.app` 存在,可执行一次 `open -a "/Applications/ziniao.app"` 启动桌面客户端,然后重新运行 `ziniao-cli doctor`;不得把 `open` 当作 Bridge 就绪证明。其他平台提示用户手动启动紫鸟客户端,用户完成后重新运行 `ziniao-cli doctor`。只有找到 `ziniao` 命令时,才执行一次:
```bash
ziniao start
ziniao-cli doctor
```
`ziniao start` 只用于启动或唤起单实例紫鸟客户端,不能修复 API Key、终端绑定、账号不一致或登录会话问题。Bridge 可能在客户端启动后才就绪;启动后可短间隔重新运行 `doctor`,总等待时间最多 30 秒。若 `ziniao start` 返回 Windows 错误 1113 或其他启动错误,或命令成功返回但 macOS 上 `doctor` 仍明确显示客户端未启动,可在 `/Applications/ziniao.app` 存在时额外执行一次 `open -a "/Applications/ziniao.app"`,随后重新运行 `doctor`;不得再次重试 `ziniao start`。仍未通过则报告启动错误和 doctor 明细后停止。
如果 ZClaw Bridge 暂时不可连接,不要再次执行 `ziniao start`,也不要执行 `zclaw login`;只短间隔重新运行 `doctor`,总等待时间最多 30 秒,仍未恢复则停止并报告。即使同一次 `doctor` 还显示客户端未登录,也必须先等 Bridge 恢复。Bridge 恢复后的下一步只能是读取最新一次 `doctor` 明细并重新分流:仍未登录才执行 `ziniao-cli zclaw login`,全部通过才执行原业务命令;禁止在复查前预先执行或承诺执行 `store open`。不要把启动或登录当作状态探测。
独立的运行中会话失效分支:如果此前 `doctor` 明细已经通过,但原始 `store`、`page` 或其他 ZClaw 本地命令随后明确返回 `Ziniao user is not logged in`,下一条命令必须逐字为:
```bash
ziniao-cli zclaw login
```
这里同样只能使用完整命令 `ziniao-cli zclaw login`,不得写成不存在的 `ziniao-cli login`。登录成功后,使用相同参数只重试刚才失败的原命令一次;不要为了这次恢复额外执行 `doctor`。不得在尚未尝试 `zclaw login` 前要求用户手工登录。
若 `zclaw login` 失败,直接报告其实际错误。API Key 无效或被拒绝、终端未绑定、账号不一致,或 Bridge 不可连接时,必须立即停止,禁止重复登录、重复发送店铺请求,或笼统要求用户“先登录客户端”。只有原始错误不是该精确未登录错误、或登录命令本身无法恢复时,才按对应原因要求用户处理前置条件。
只有 `doctor` 显示配置、API Key、服务端连通性、ZClaw Bridge 和客户端账号一致时,才继续浏览器操作:
- 店铺窗口操作使用 `ziniao-store` Skill。
- 页面浏览器自动化使用 `ziniao-page` Skill。
- 查询可用 Bridge 工具使用 `ziniao-cli zclaw tools`;仅在没有快捷命令或用户明确要求调试原始工具时,才使用 `ziniao-cli zclaw invoke <tool>`。
| 现象 | 处理 |
|------|------|
| `config init --new` 提示客户端未登录 | 启动并登录紫鸟客户端,再重新执行初始化。 |
| `doctor` 提示客户端未启动或本地服务未就绪 | 按当前 Shell 使用 PowerShell `Get-Command`、CMD `where.exe` 或 POSIX/Git Bash `command -v` 检测;找到命令才执行一次 `ziniao start`。若 macOS 上 `ziniao start` 失败或执行后仍未启动,且存在 `/Applications/ziniao.app`,可再执行一次 `open -a "/Applications/ziniao.app"`,随后重新运行 `doctor`;若 macOS 找不到命令但应用存在,也直接使用一次 `open`;其他情况提示用户手动启动。 |
| `doctor` 明确显示客户端已启动但未登录,且 Bridge 已连通 | 直接执行一次 `ziniao-cli zclaw login`,不得先发店铺或页面命令探测;登录后重新运行 `doctor`,明细全部通过后才执行原业务命令。 |
| `127.0.0.1:9481` 暂不可连接(无论客户端是否同时显示未登录) | 不启动、不登录;短间隔重新运行 `doctor`,最多等待 30 秒。Bridge 恢复后再按最新客户端明细分流。 |
| `ziniao start` 返回 Windows 错误 1113 或其他启动错误 | 不重试启动;立即重新运行一次 `doctor`,明细通过则继续,否则同时报告启动错误和 doctor 明细后停止。 |
| `zclaw login` 提示无法连接 ZClaw Bridge | 不把登录当作启动探测;回到 `doctor` 判断 9481 不可连接的具体原因。 |
| API Key 无效或未配置 | 使用正确的 Key 重新执行 `ziniao-cli config init`,或重新运行 `ziniao-cli config init --new`。 |
| 初始化后 ZClaw 仍提示 API Key 认证失败 | 前往 https://open.ziniao.com 的用户应用「终端管理」,绑定当前终端识别码(在紫鸟浏览器设置中查看),然后重新执行 `ziniao-cli zclaw login` 和 `ziniao-cli doctor`。 |
| `doctor` 提示客户端登录用户与当前 profile 不一致 | 明确说明当前 profile 与客户端登录账号及其不一致,询问用户是否切换到当前 profile 对应账号并继续原请求;不要只要求用户手工切换。用户确认后按既有登录流程继续。 |
### 多账号切换
支持多个账号配置(profile),通过 `config use` 切换:
```bash
# 列出所有 profile
ziniao-cli config list
# * zhangsan
# staging
# 切换到指定 profile
ziniao-cli config use staging
# 重命名 profile
ziniao-cli config rename staging production
```
初始化时通过 `--profile` 指定名称,浏览器创建流程会自动用账号用户名命名。
### 删除配置
删除操作会弹出确认提示,`--yes` 可跳过:
```bash
ziniao-cli config remove # 删除当前 profile(需确认)
ziniao-cli config remove --profile staging # 删除指定 profile(需确认)
ziniao-cli config remove --yes # 跳过确认直接删除
```
## 认证
### 认证模型
ziniao-cli 使用**统一 apiKey**(Bearer Token),一个 Key 同时用于:
| 用途 | 地址 | 说明 |
|------|------|------|
| 服务端 API | `sbappstoreapi.ziniao.com` | 部门/员工/账号/设备等业务接口 |
| 本地 ZClaw Bridge | `127.0.0.1:9481` | 紫鸟浏览器店铺/页面操控 |
**没有** OAuth、token 刷新、双身份(user/bot)等复杂机制。apiKey 是静态凭证,不过期。
### ISV 应用权限点
调用服务端 API 前,需在紫鸟开放平台为应用开通对应的权限点。这些权限仅 Boss 账号可申请和调用。以下是各模块所需的权限点:
| 模块 | 权限点 | 覆盖接口 |
|------|--------|---------|
| 部门员工 | ERP-部门与员工接口 | 部门 CRUD + 员工查询/新增/修改/启禁用(9 个) |
| 部门员工 | ERP-用户的部门变更 | 员工调岗(1 个) |
| 角色权限 | ERP-角色列表查询 | 角色列表 + 用户角色列表(2 个) |
| 角色权限 | ERP-角色详情 | 角色详情(1 个) |
| 角色权限 | ERP-权限列表 | 权限项列表(1 个) |
| 角色权限 | ERP-角色添加、修改权限 | 添加/修改/调整角色(3 个) |
| 设备管理 | ERP-设备查询 | 设备列表 + 历史绑定记录(2 个) |
| 设备管理 | ERP-设备套餐列表查询权限 | 套餐列表(1 个) |
| 设备管理 | ERP-设备绑定权限 | 绑定设备(1 个) |
| 设备管理 | ERP-解绑设备 | 解绑设备(1 个) |
| 设备管理 | ERP-开关自动续费 | 自动续费开关(1 个) |
| 设备管理 | ERP-设备购买与续费权限 | 购买 + 续费(2 个) |
| 设备管理 | ERP-添加自有设备(新) | 添加自有设备(1 个) |
| 设备管理 | ERP-修改自有设备信息(新) | 修改自有设备(1 个) |
| 设备管理 | ERP-查询已购设备价格接口 | 已购设备价格(1 个) |
| 账号管理 | ERP-账号查看权限 | 账号列表/授权查询/用户账号列表/授权用户列表(4 个) |
| 账号管理 | ERP-创建与删除账号权限 | 创建 + 删除账号(2 个) |
| 账号管理 | ERP-编辑账号基础信息 | 编辑账号信息(1 个) |
| 账号管理 | ERP-账号授权权限 | 授权新增 + 授权删除(2 个) |
| 账号管理 | ERP-清除账号授权 | 清除全部授权(1 个) |
| 账号管理 | ERP-清除账号缓存 | 清除缓存(1 个) |
| 账号管理 | ERP-标签列表 | 企业标签列表(1 个) |
| 账号管理 | ERP-查询某用户有权限的账号列表 | 用户有权限的账号(1 个) |
| 账号管理 | ERP-获取附加网站信息 | 附加网站信息(1 个) |
| 账号管理 | 账号标签管理权限 | 标签 CRUD + 绑定/解绑/替换/清空/移除(9 个) |
| 访问策略 | ERP-网页访问权限 | 访问规则/网页/网页分组全部操作(22 个) |
> 如果调用接口返回 `isv.invalid-method`(不存在的方法名),通常是该权限点未开通。前往 [紫鸟开放平台](https://open.ziniao.com) → 应用管理 → 权限管理 中开通。
### 公共参数
每个服务端 API 请求都需要 `companyId`,**CLI 自动注入并强制使用配置值**,无需也不应手动传递。该值在 `config init` 时通过 `/app/builtin/company` 接口自动获取并写入配置;即使 `--data` 中显式传入 `companyId`,CLI 也会用配置值覆盖。
## 两层命令体系
### 第一层:通用 api 命令(覆盖全部 73 个接口)
任何紫鸟服务端 API 都可以通过 `api` 命令调用,无需专门的快捷命令:
```bash
ziniao-cli api <path> [--data '{}'] [--format table] [--jq '.data[]']
ziniao-cli api GET /app/builtin/company
ziniao-cli api /superbrowser/rest/v1/erp/department/list
ziniao-cli api /superbrowser/rest/v1/erp/staff/list --data '{"page":1,"limit":10}' --format table
```
- 默认 POST 方法,支持 GET/POST/PUT/DELETE
- `companyId` 由框架自动注入,并强制覆盖 `--data` 中的同名字段
- `--page-all` 自动翻页(默认最多 10 页,需取全部时配合 `--page-limit 0`)
- `--page-size N` 每页条数(默认 20)
- `--page-limit N` 最大翻页数(默认 10,0 为不限,配合 `--page-all`)
- `--page-delay MS` 翻页间隔毫秒数(默认 200,配合 `--page-all`)
- `--dry-run` 预览请求不执行
- `--jq` 内置 jq 过滤
### 第二层:快捷命令(高频场景优化)
为复杂接口提供命名 flag + 智能默认值:
```bash
ziniao-cli department list --tree
ziniao-cli staff create --username "zhangsan" --name "张三" --password "Pass123!" --role-id 16691047257645
ziniao-cli store list --format table
```
## `account` 与 `store` 的区别
两组命令都涉及"店铺",但职责和通道完全不同:
| | `account` 命令 | `store` 命令 |
|--|---------------|-------------|
| 通道 | 服务端 API(`sbappstoreapi.ziniao.com`) | 本地 ZClaw Bridge(`127.0.0.1:9481`) |
| 职责 | 店铺账号的 CRUD、授权、标签等**管理操作** | 控制已打开的浏览器实例:列出、打开、关闭 |
| 前置条件 | 只需 apiKey + 网络 | 紫鸟浏览器客户端必须已启动 |
| 典型场景 | 创建店铺、批量授权员工、管理标签 | 打开店铺浏览器 → 导航 → 截图 → 自动化操作 |
**简单记忆:** `account` = 管理后台增删改查,`store` = 控制本地浏览器窗口。
## 命令优先级
AI Agent 调用时按优先级选择:
1. **快捷命令** -- `staff list`、`department create`、`store open` 等(参数简化,体验最好)
2. **通用 api 命令** -- `api <path>` 兜底(任意接口都能调,需要手写 JSON body)
3. **zclaw invoke** -- `zclaw invoke <tool>` 兜底(任意 ZClaw 工具都能调)
### ZClaw 快捷命令硬约束
若某个 ZClaw 工具已有对应快捷命令,**必须只调用快捷命令**,禁止再调用
`ziniao-cli zclaw invoke <tool>`。两者最终都会请求同一个 Bridge 工具;重复调用会重复执行操作。
`zclaw invoke` 仅用于没有快捷命令的 Bridge 工具,或用户明确要求按原始工具名和 JSON 参数调试。
例如 `visit_page` 已由 `page visit` 封装,必须使用 `ziniao-cli page visit ...`。
### 店铺环境结果硬约束
当用户要求店铺浏览器、店铺代理或该店铺登录态下的结果时,结果只能来自该店铺环境。
店铺页面操作失败后,禁止改用公开网页、搜索引擎、其他店铺、服务端 API 或供应来源来“替代”结果;除非用户明确将任务范围改为公开信息检索。
发生 `ERR_SOCKS_CONNECTION_FAILED` 时,保持当前店铺打开,等待 5 秒后以相同参数重试;最多 3 次总尝试。仍失败时,报告该店铺环境暂时无法访问及已尝试次数,并停止该店铺任务;不得声称已取得店铺环境结果。
## 输出格式
所有命令支持 `--format json|table|csv` 和 `--jq` 过滤:
```bash
ziniao-cli staff list --format table
ziniao-cli staff list --jq '.[].name'
ziniao-cli department list --format csv
```
### 输出结构
成功(stdout):
```json
{"ok": true, "data": ..., "meta": {"count": 10}}
```
失败(stderr):
```json
{"ok": false, "error": {"type": "gateway|business|auth|validation", "code": 1001, "message": "...", "hint": "..."}}
```
### 错误类型与处理
| 错误类型 | 含义 | AI Agent 应该做什么 |
|---------|------|-------------------|
| `auth` | apiKey 缺失或无效 | 提示用户运行 `ziniao-cli config init` |
| `gateway` | 网关层错误 (code != "0") | 报告错误,检查网络/apiKey |
| `business` | 业务层错误 (ret != 0) | 报告错误信息,根据 msg 判断原因 |
| `validation` | 参数校验失败 | 检查命令参数是否正确 |
| `network` | 网络不通/Bridge 未启动 | ZClaw 相关:回到 `doctor` 明细路由,只有客户端未启动时才进入启动流程;API 相关:检查网络 |
## 更新检查
ziniao-cli 命令执行后,如果检测到新版本,JSON 输出中会包含 `_notice.update` 字段:
```json
{
"ok": true,
"data": ...,
"_notice": {
"update": {
"current": "1.0.0",
"latest": "1.1.0",
"message": "ziniao-cli 1.1.0 可用,当前 1.0.0,运行 npm update -g @ziniao-open/cli 更新"
}
}
}
```
**当你在输出中看到 `_notice.update` 时:**
1. 先完成用户当前请求
2. 然后将 `message` 字段内容展示给用户,提议帮其更新
3. 若用户同意,执行 `npm update -g @ziniao-open/cli`
更新提示仅通过 stdout JSON 的 `_notice` 字段传递,不会输出到 stderr。可通过环境变量 `ZINIAO_CLI_NO_UPDATE_CHECK=1` 禁用检查。
## 环境兼容说明
### Windows Git Bash 路径转义问题
在 **Git Bash** 中,以 `/` 开头的字符串会被 MSYS 自动转换为 Windows 本地路径(如 `/superbrowser/...` → `C:/Program Files/Git/superbrowser/...`),导致 `api` 命令的路径参数被破坏。
**PowerShell 和 CMD 无此问题。**
**解决方式一(推荐):写入 `.bashrc` 永久生效**
```bash
echo 'export MSYS_NO_PATHCONV=1' >> ~/.bashrc
source ~/.bashrc
```
**解决方式二:每次命令前加前缀**
```bash
MSYS_NO_PATHCONV=1 ziniao-cli api /superbrowser/rest/v1/erp/store/create \
--data '{"storeData":[{"name":"新店铺"}]}'
```
## 安全规则
- **API Key 是静态敏感凭证**:不得请求用户在聊天中发送完整 Key,也不得在回复、终端输出或错误信息中展示完整 Key
- **禁止把完整 API Key 写入命令行参数、Shell 历史、脚本、截图、录屏、聊天消息、日志或 Git**
- 人工交互环境使用 `ziniao-cli config init` 输入 Key;输入期间避免屏幕共享、录屏和终端日志采集
- CI/CD 使用 `ziniao-cli config init --api-key-stdin`,由 Secret Manager 或受保护的标准输入提供 Key;不得把 Key 直接写在流水线命令中
- **写入/删除操作前必须确认用户意图**
- `high-risk-write` 操作(department delete、staff remove)会要求交互式确认,可用 `--yes` 跳过
- 建议先用 `--dry-run` 预览危险请求
## 重要行为规则
- **ZClaw 本地接口必须通过 ziniao-cli 调用**:调用紫鸟浏览器本地接口(store/page/zclaw 命令)时,必须使用本技能体系中的 ziniao-cli 能力,不要使用 ziniao-assistant 技能自行调用 ZClaw Bridge。
- **店铺列表优先使用本地接口**:如果用户要求获取店铺列表,应优先使用 `store list` 快捷命令(走本地 ZClaw Bridge),因为普通成员没有服务端 `account list` 接口权限。成员类型可通过 `ziniao-cli config show` 结果中的 `isBoss` 字段判断。
- **ZClaw 认证失败排查**:如果帮用户初始化应用(`config init`)之后,请求 ZClaw 接口仍返回 API Key 认证失败,应提醒用户前往紫鸟开放平台 https://open.ziniao.com 查看自己的用户应用里「终端管理」是否已绑定当前终端识别码(识别码在紫鸟浏览器设置中查看)。
@@ -0,0 +1,81 @@
---
name: ziniao-skill-maker
version: 1.0.0
description: "创建 ziniao-cli 的自定义 Skill。当用户需要把紫鸟 API 操作封装成可复用的 Skill(包装原子 API 或编排多步流程)时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# Skill Maker
基于 ziniao-cli 创建新 Skill。Skill = 一份 `SKILL.md`,教 AI 用 CLI 命令完成任务。
## CLI 核心能力
```bash
ziniao-cli <service> <command> # 快捷命令
ziniao-cli api [METHOD] <path> [--data/--format] # 任意服务端 API
ziniao-cli zclaw invoke <tool> [--args] # 任意 ZClaw 工具
ziniao-cli store/page <command> # ZClaw 快捷命令
```
优先级:快捷命令 > 通用 api/zclaw invoke。
## 调研 API
```bash
# 1. 查看已有快捷命令
ziniao-cli department --help
ziniao-cli staff --help
# 2. 查看 ZClaw 工具
ziniao-cli zclaw tools
# 3. 通用 api 裸调
ziniao-cli api /superbrowser/rest/v1/erp/per/role/list
```
如果命令不够用,使用 [ziniao-openapi-explorer](../ziniao-openapi-explorer/SKILL.md) 查找 API 路径。
## SKILL.md 模板
文件放在 `skills/ziniao-<name>/SKILL.md`:
```markdown
---
name: ziniao-<name>
version: 1.0.0
description: "<功能描述>。当用户需要<触发场景>时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# <标题>
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)**
## 命令
\```bash
# 单步操作
ziniao-cli api POST /superbrowser/rest/v1/erp/xxx --data '{...}'
# 多步编排
# Step 1: ...(记录返回的 xxx_id)
# Step 2: 使用 Step 1 的 xxx_id
\```
## 参考
- [ziniao-shared](../ziniao-shared/SKILL.md) — 认证和全局参数
```
## 关键原则
- **description 决定触发** — 包含功能关键词 + "当用户需要...时使用"
- **CRITICAL 依赖** — 所有 Skill 必须强制读取 ziniao-shared
- **安全** — 写入操作前确认用户意图
- **编排** — 说明步骤间的数据传递和失败处理
+77
View File
@@ -0,0 +1,77 @@
---
name: ziniao-staff
version: 1.0.0
description: "紫鸟员工管理:员工的查询、创建、修改、启用/禁用/删除和调岗。当用户需要查询员工信息、创建新员工、修改员工信息、启用/禁用/删除员工或调整员工部门时使用。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 员工管理
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md),其中包含认证、错误处理和安全规则**
**CRITICAL — 所有 Shortcuts 在执行之前,务必先用 Read 工具读取其对应的 references/ 说明文档,禁止直接盲目调用命令。**
## 核心场景
### 查询员工
支持多种筛选条件,枚举值已映射为人类可读。注意:默认返回包含已删除员工,取 ID 做后续操作时建议加 `--status active`。
```bash
ziniao-cli staff list --name "张三" --status active --format table
ziniao-cli staff list --role boss --page-all
ziniao-cli staff list --department-id 15868464646076
```
### 创建员工
原始 API 需要 20+ 字段的嵌套 JSON,CLI 简化为核心 flag + 智能默认值(`--department-id` 必填):
```bash
ziniao-cli staff create --username "zhangsan" --name "张三" --password "Pass123!" --role-id 16691047257645 --department-id 15868464646076
```
智能默认值:`authDevide=1`(全设备授权)、`authClient=全部允许`、`isLimitLogin=0`。
登录日期默认发送有效日期:`loginLimitStartDate=今天`、`loginLimitEndDate=2099-12-31`,也可通过 `--login-start-date` / `--login-end-date` 指定。
### 启用/禁用/删除
原始 API 是一个接口通过 `status` 字段区分,CLI 拆为 3 个语义化命令:
```bash
ziniao-cli staff enable --id 16524289555087
ziniao-cli staff disable --id "id1,id2,id3" # 支持批量
ziniao-cli staff remove --id 16524289555087 # high-risk-write
```
## 枚举映射
| flag | 可选值 | 对应 API 值 |
|------|--------|-----------|
| `--status` | active / disabled / deleted | delflag: 0/1/2 |
| `--role` | boss / manager / employee / admin | level: 0/1/2/3 |
| `--device-auth` | auto / first / approve / phone | authDevide: 1/2/3/4 |
| `--allow-clients` | windows,mac,web,android,ios,linux,miniapp 或 all | authClient 嵌套对象 |
## Shortcuts
| 命令 | 说明 | 详细文档 |
|------|------|---------|
| `staff list` | 查询员工列表 | [`references/ziniao-staff-list.md`](references/ziniao-staff-list.md) |
| `staff create` | 新增员工 | [`references/ziniao-staff-create.md`](references/ziniao-staff-create.md) |
| `staff update` | 修改员工 | 只发修改字段:`--id xxx --name "新名"`,底层路径 `/superbrowser/rest/v2/erp/staff/modify` |
| `staff enable` | 启用员工 | [`references/ziniao-staff-enable-disable.md`](references/ziniao-staff-enable-disable.md) |
| `staff disable` | 禁用员工 | 同上 |
| `staff remove` | 删除员工 | `high-risk-write` |
| `staff transfer` | 调岗 | `--staff-id xxx --department-id yyy` |
## 通用 api 覆盖
```bash
ziniao-cli api /superbrowser/rest/v1/erp/staff/list --data '{"page":1,"limit":10}'
ziniao-cli api /superbrowser/rest/v2/erp/staff/add --data '{...}'
ziniao-cli api /superbrowser/rest/v2/erp/staff/modify --data '{...}'
ziniao-cli api /superbrowser/rest/v1/erp/staff/status --data '{"staffIds":["xxx"],"status":"1"}'
ziniao-cli api /superbrowser/rest/v1/erp/staff/department --data '{"staffIds":["xxx"],"departmentIds":["yyy"]}'
```
@@ -0,0 +1,60 @@
# staff create
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
新增员工。写入操作。原始 API 需要 20+ 字段,CLI 简化为核心 flag,并补齐服务端需要的登录日期字段。
## 命令
```bash
# 最简用法(5 个必填参数)
ziniao-cli staff create \
--username "zhangsan" \
--name "张三" \
--password "Pass123!" \
--role-id 16691047257645 \
--department-id 15868464646076
# 指定部门 + 限制登录终端
ziniao-cli staff create \
--username "zhangsan" \
--name "张三" \
--password "Pass123!" \
--role-id 16691047257645 \
--department-id 15868464646076 \
--allow-clients windows,mac,web
```
## 参数
| 参数 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| `--username` | 是 | — | 登录账号 |
| `--name` | 是 | — | 姓名 |
| `--password` | 是 | — | 密码 |
| `--role-id` | 是 | — | 角色 ID(用 `ziniao-cli role list` 或 `ziniao-cli api /superbrowser/rest/v1/erp/per/role/list` 查询) |
| `--department-id` | 是 | — | 部门 ID |
| `--mobile` | 否 | 无 | 手机号 |
| `--allow-clients` | 否 | all | 允许登录终端(windows,mac,web,android,ios,linux,miniapp) |
| `--device-auth` | 否 | auto | 设备授权模式(auto\|first\|approve\|phone) |
| `--login-start-date` | 否 | 今天 | 可登录起始日期,格式 yyyy-MM-dd |
| `--login-end-date` | 否 | 2099-12-31 | 可登录结束日期,格式 yyyy-MM-dd |
## 智能默认值
以下字段使用智能默认值,用户无需指定:
| 原始 API 字段 | 默认值 | 说明 |
|--------------|--------|------|
| `authDevide` | 1 | 全设备自动永久授权 |
| `isUpdatePersonInfo` | 1 | 允许修改个人信息 |
| `isLimitLogin` | 0 | 不限制登录时间 |
| `isTwoStepVerify` | 0 | 不开启二步验证 |
| `enablePhoneLogin` | 0 | 不开启手机号登录 |
| `authClient` | 全部 "1" | 允许所有终端 |
| `loginLimitStartDate` | 今天 | 服务端需要非空有效日期 |
| `loginLimitEndDate` | 2099-12-31 | 服务端需要非空有效日期 |
## 参考
- [ziniao-staff](../SKILL.md) — 员工管理全部命令
@@ -0,0 +1,47 @@
# staff enable / disable / remove
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
启用、禁用或删除员工。原始 API 是一个接口(`/erp/staff/status`),CLI 拆为 3 个语义化命令。
## 命令
```bash
# 启用
ziniao-cli staff enable --id 16524289555087
# 禁用(支持批量)
ziniao-cli staff disable --id 16524289555087
ziniao-cli staff disable --id "id1,id2,id3"
# 删除(高风险,需确认)
ziniao-cli staff remove --id 16524289555087
ziniao-cli staff remove --id 16524289555087 --yes # 跳过确认
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--id` | 是 | 员工 ID(逗号分隔可批量) |
| `--yes` | 否 | 跳过确认(仅 remove) |
## 风险等级
| 命令 | 风险等级 | 是否需确认 |
|------|---------|----------|
| `staff enable` | write | 否 |
| `staff disable` | write | 否 |
| `staff remove` | high-risk-write | 是(交互式确认) |
## 内部映射
| 命令 | API status 值 |
|------|--------------|
| enable | "0" |
| disable | "1" |
| remove | "2" |
## 参考
- [ziniao-staff](../SKILL.md) — 员工管理全部命令
@@ -0,0 +1,64 @@
# staff list
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md)。
查询员工列表。只读操作。
## 命令
```bash
# 查询全部
ziniao-cli staff list
# 按姓名搜索(模糊)
ziniao-cli staff list --name "张三"
# 精确匹配
ziniao-cli staff list --name "张三" --exact
# 按状态筛选 + 表格输出
ziniao-cli staff list --status active --format table
# 按角色筛选
ziniao-cli staff list --role boss
# 按部门筛选
ziniao-cli staff list --department-id 15868464646076
# 自动翻页
ziniao-cli staff list --page-all --page-size 50
# jq 过滤
ziniao-cli staff list --jq '.[].name'
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 否 | 按姓名搜索 |
| `--username` | 否 | 按账号搜索 |
| `--department-id` | 否 | 按部门 ID 筛选(逗号分隔) |
| `--status` | 否 | active \| disabled \| deleted |
| `--role` | 否 | boss \| manager \| employee \| admin |
| `--exact` | 否 | 精确匹配(默认模糊) |
| `--page-size` | 否 | 每页条数(默认 20) |
| `--page-all` | 否 | 自动翻页获取全部 |
| `--page-limit` | 否 | 最大翻页数(默认 10,0 为不限) |
| `--page-delay` | 否 | 翻页间隔毫秒数(默认 200) |
| `--format` | 否 | json \| table \| csv |
| `--jq` / `-q` | 否 | jq 过滤表达式 |
## 输出字段翻译
表格输出时,枚举值自动翻译:
- `level "0"` -> `role: "Boss"`
- `delflag "0"` -> `statusText: "正常"`
## 注意事项
- 默认返回所有状态的员工(含已禁用和已删除)。如果需要取员工 ID 做后续操作(分配角色、授权店铺等),建议加 `--status active` 筛选,避免使用已删除员工 ID 导致"无效的 staffId"错误。
## 参考
- [ziniao-staff](../SKILL.md) — 员工管理全部命令
+47
View File
@@ -0,0 +1,47 @@
---
name: ziniao-store
description: "Use when a request concerns the local 紫鸟/ZClaw browser, a store name or ID, listing/resolving/opening/reusing/closing a store browser, or a store-scoped task that starts from a store. Do not use for server-side account CRUD or for a webpage task on an already-open store."
metadata:
requires:
bins: ["ziniao-cli"]
---
# 紫鸟店铺入口
本技能负责“店铺”这一资源及其本地浏览器生命周期,不负责定义用户的业务目标,也不复制 CLI 帮助文档。
## 负责范围
- 列出、解析、打开、复用、关闭本地紫鸟店铺浏览器。
- 保存本次任务确认过的 `store-id`,供后续步骤复用。
- 用户从某个店铺开始并继续操作网页时,按需使用 `ziniao-page`。
- 任务涉及配置、认证、Bridge、客户端或网络异常时,按需读取 `ziniao-shared` 的诊断规则。
## 路由边界
- 请求以店铺名称/ID或“本地紫鸟店铺”为起点:先处理店铺,再判断是否需要页面能力。
- 已有明确且正在运行的店铺,只需网页操作:使用 `ziniao-page`,不要重复打开店铺。
- 店铺后台账号的创建、删除、授权、标签等服务端管理:使用 `ziniao-account`,不是本技能。
- 不要把用户的业务目标或任务参数写入本技能;这些属于任务内容,不是店铺能力。
## 最小执行规则
1. 需要店铺对象时,使用 CLI 的 `store list` 或 `store resolve` 获取并确认目标。
2. 需要浏览器窗口时,使用 `store open`;已有有效 `store-id` 且店铺仍运行时直接复用。
3. 需要网页资源操作时,继续使用 `ziniao-page`;不要在本技能中重述页面命令。
4. 店铺命令失败时,根据错误按需运行 `ziniao-cli doctor`;不要无条件登录、启动或重复重试。
5. 具体参数不确定时,使用 `ziniao-cli store <command> --help`,复杂命令再读取对应 reference。
## 打开后的验证边界
- 用户只要求打开店铺时,精确解析店铺并成功执行带 `--expected-name` 的 `store open` 即可结束;不得为了证明窗口存在而追加 `page snapshot`。
- 用户同时给出目标 URL 时,首次打开优先使用同一次 `store open --url`,不要再对同一 URL 重复执行 `page visit`。
- 用户要求确认最终页面或业务状态时,转到 `ziniao-page`,使用导航返回值、`page content` 或 `page exec` 验证 URL/title/正文;不得用 snapshot 代替普通页面验证。
- 只有后续页面任务本身满足上传、自定义控件或复杂歧义定位条件时,才由 `ziniao-page` 决定使用 snapshot。
## 按需参考
- [store list](references/ziniao-store-list.md):需要列出店铺时读取。
- [store open](references/ziniao-store-open.md):需要打开或复用店铺时读取。
- `ziniao-shared`:仅在配置、认证、Bridge 或诊断问题出现时读取。
- `ziniao-page`:仅在任务继续操作已打开店铺内网页时读取。
@@ -0,0 +1,50 @@
# store list
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md),再检查店铺操作所需状态:
>
> ```bash
> ziniao-cli doctor
> ```
>
> `doctor` 明细确认通过后直接执行 `store list`,不得无条件启动或登录。检查未通过时,严格按照 `ziniao-shared` 的「ZClaw 客户端与 Bridge 就绪」流程处理;只有客户端未登录或 `store list` 明确返回会话缺失/过期时,才允许登录一次,随后重新检查并只重试一次。API Key、终端或账号认证失败时立即停止。
列出所有店铺。只读操作。
## 命令
```bash
ziniao-cli store list
ziniao-cli store list --format table
ziniao-cli store list --keyword "Rose" --limit 10
ziniao-cli store list --all
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--page` | 否 | 页码 |
| `--limit` | 否 | 每页数量 |
| `--all` | 否 | 获取全部店铺 |
| `--keyword` | 否 | 按关键词筛选 |
| `--type` | 否 | 店铺列表类型 |
| `--format` | 否 | 输出格式: json \| table |
| `--jq` | 否 | jq 过滤表达式 |
## 输出字段
| 字段 | 说明 |
|------|------|
| storeId | 店铺 ID(后续操作需要) |
| storeName | 店铺名称 |
| platformName | 平台名称 |
| ip | 绑定的 IP |
## 提示
- storeId 是后续所有页面操作(page visit/click/screenshot 等)的必填参数
- 建议先用 `store list --format table` 查看所有店铺
## 参考
- [ziniao-store](../SKILL.md) — 店铺管理全部命令
@@ -0,0 +1,48 @@
# store open
> **前置条件:** 先阅读 [`../ziniao-shared/SKILL.md`](../../ziniao-shared/SKILL.md),再检查店铺操作所需状态:
>
> ```bash
> ziniao-cli doctor
> ```
>
> `doctor` 明细确认通过后直接执行 `store open`,不得无条件启动或登录。检查未通过时,严格按照 `ziniao-shared` 的「ZClaw 客户端与 Bridge 就绪」流程处理;只有客户端未登录或 `store open` 明确返回会话缺失/过期时,才允许登录一次,随后重新检查并只重试一次。API Key、终端或账号认证失败时立即停止。
打开或复用店铺浏览器窗口;带 `--url` 时导航到目标页面。
> 首次进入目标页面时,优先使用 `store open --url <url>`,即使店铺为 `reused: true` 也会直接导航当前页面。已建立会话后的后续 URL 跳转使用 `page visit`。同一个 URL 不得连续执行 `store open --url` 与 `page visit`。
## 命令
```bash
# 按名称打开
ziniao-cli store open --name "Rosehut"
# 按 ID 打开
ziniao-cli store open --id abc123
# 打开并直接访问 URL
ziniao-cli store open --name "Rosehut" --url "https://www.amazon.com"
# 无头模式打开
ziniao-cli store open --name "Rosehut" --headless
# 隐私模式打开
ziniao-cli store open --name "Rosehut" --privacy
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 否* | 店铺名称(与 --id 二选一) |
| `--id` | 否* | 店铺 ID(与 --name 二选一) |
| `--expected-name` | 否 | 期望匹配的名称(用于验证) |
| `--url` | 否 | 打开后直接导航到的 URL(对应 launchUrl) |
| `--headless` | 否 | 无头模式 |
| `--privacy` | 否 | 隐私模式 |
| `--window-ratio` | 否 | 窗口比例 |
## 参考
- [ziniao-store](../SKILL.md) — 店铺管理全部命令
@@ -0,0 +1,80 @@
---
name: ziniao-workflow-batch-account
version: 1.0.0
description: "批量账号授权工作流:编排 account list + staff list + api 调用完成批量授权店铺给员工。适用于新员工入职时批量分配店铺权限。"
metadata:
requires:
bins: ["ziniao-cli"]
---
# 批量账号授权工作流
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../ziniao-shared/SKILL.md`](../ziniao-shared/SKILL.md)**
## 适用场景
- "给新员工分配店铺权限"
- "批量授权店铺给某个员工"
- "新人入职,需要授权所有店铺"
## 前置条件
- 已配置 apiKey(`ziniao-cli config init`)
## 工作流
```
staff list ──► 找到目标员工 ID
account list ──► 找到目标店铺 ID 列表
│
├──► 对每个店铺:
│ api /superbrowser/rest/v1/erp/store/auth/add { storeIdList, staffId }
│
└──► 汇总授权结果
```
### Step 1: 查找员工
```bash
# 按姓名查找
ziniao-cli staff list --name "张三" --format table
# 记录 userId
```
### Step 2: 查找要授权的店铺
```bash
# 列出所有店铺
ziniao-cli account list --format table --page-all
# 或按名称搜索
ziniao-cli account list --name "US" --format table
```
### Step 3: 逐个授权
对每个店铺执行:
```bash
ziniao-cli api /superbrowser/rest/v1/erp/store/auth/add \
--data '{"storeIdList":["<storeId>"],"staffId":"<staffId>"}'
```
### Step 4: 验证
```bash
# 查看该员工已授权的店铺
ziniao-cli api /superbrowser/rest/v1/erp/user/stores \
--data '{"userId":"<userId>","page":1,"limit":10}'
```
## 注意事项
- 授权是写入操作,执行前确认用户意图
- 如果需要授权大量店铺,建议先列出计划并确认
- 授权失败时记录错误并继续下一个
## 参考
- [ziniao-account](../ziniao-account/SKILL.md) — 账号管理
- [ziniao-staff](../ziniao-staff/SKILL.md) — 员工管理
- [ziniao-shared](../ziniao-shared/SKILL.md) — 认证和全局参数