213 lines
7.5 KiB
Markdown
213 lines
7.5 KiB
Markdown
# 与其它 Skill 集成(account-manager)
|
||
|
||
## 核心约束
|
||
|
||
业务 skill **不能自己散落存储账号、密码、API Key、Token**。应通过 account-manager **CLI**(进程隔离)或 **CLI + `service.rpa_helpers` 同进程库**(浏览器自动化)组合获取。
|
||
|
||
## 两种集成方式
|
||
|
||
| 方式 | 适用场景 | 调用形态 |
|
||
|---|---|---|
|
||
| **A. CLI subprocess(v1 起)** | 任意业务 skill;一次性取账号 JSON / 凭据 / lease | `subprocess.run([sys.executable, f"{skill_root}/scripts/main.py", "account", "pick-web", ...])` |
|
||
| **B. rpa_helpers 同进程 import(v2)** | 需要 Playwright 上下文内完成登录与页面操作 | `sys.path.insert(0, f"{account_manager_root}/scripts"); from service.rpa_helpers import ensure_logged_in` |
|
||
|
||
常见组合:**CLI** 负责 pick / lease / `get-credential --reveal`;**rpa_helpers** 负责 `launch_browser_with_profile` + `ensure_logged_in`。详见 [`RPA_HELPERS.md`](RPA_HELPERS.md)。
|
||
|
||
## 推荐调用方式
|
||
|
||
使用 **`subprocess`** 调用 `scripts/main.py`。宿主注入的 **`JIANGCHANG_DATA_ROOT` / `JIANGCHANG_USER_ID`**(或上层包装的 `CLAW_*`)必须与最终用户会话一致——否则会写到另一套目录。路径调试见 [`RUNTIME.md`](RUNTIME.md)。
|
||
|
||
## 机器可读命令摘要
|
||
|
||
| 目的 | 命令 | 成功时 stdout(摘要) |
|
||
|---|---|---|
|
||
| 枚举平台 | `platform list [--domain ...]` | `{"success": true, "data": {"platforms": [...]}}` |
|
||
| 平台详情 | `platform get <p>` | `{"success": true, "data": {...}}` |
|
||
| 取单条账号 JSON | `account get <id>` | **单行 JSON 对象**(无固定 `success` 字段) |
|
||
| 批量账号 JSON | `account list ...` | **单行 JSON 数组** |
|
||
| 网页自动化候选 | `account pick-web ...` | **单行 JSON 对象** |
|
||
| 取加密凭据明文 | `account get-credential <id> --reveal --lease-token <t>` | **单行 JSON**,含 `plaintext`(若可解密) |
|
||
| 备份加密凭据 | `account export-credentials --plaintext` | **JSON 数组**(stderr 有警告行) |
|
||
| 导入加密凭据 | `account import-credentials [--replace-all]` | **通常为空**(计数在 stderr) |
|
||
| 初始化 master key | `account init-master-key [--print\|--from-env\|--rotate]` | **单行 JSON** |
|
||
| 新增网页账号 | `account add-web ...` | `{"success": true, "data": {...}}` |
|
||
| 新增凭据 | `account add-secret ...` | `{"success": true, "data": {...}}` |
|
||
| 获取 API Key | `credential pick ... [--reveal]` | `{"success": true, ...}` |
|
||
| 兼容入口 | `get <id>` / `list-json <p>` / `pick-web <p>` | 同上对应新式命令 |
|
||
|
||
更完整的命令表见 [`CLI.md`](CLI.md)。
|
||
|
||
## 解析协议(稳妥做法)
|
||
|
||
1. 读取子进程 **stdout**;若有多行,**集成应以首行为准**(export/import 例外:`export` 整块 stdout 即 JSON;`import` 读 stderr)。
|
||
2. **优先尝试 `json.loads`**:
|
||
- 若为 dict 且 `success is False` → 读取 `error.code` / `error.message`。
|
||
- 若 dict 且 `success is True` 且存在 `data` → 业务字段通常在 `data` 内(`add-web` / `add-secret` / `platform`)。
|
||
3. 若 JSON 首字段不是上述统一格式(例如 `pick-web`)→ 直接按字段读取。
|
||
4. **遗留文本失败**:首行以 **`ERROR:`** 开头 → 视为失败(旧别名路径仍可能出现)。
|
||
|
||
## JSON 对象字段
|
||
|
||
### `account pick-web`(典型)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"platform_key": "maersk",
|
||
"account_label": "Maersk simulator booking",
|
||
"login_id": "demo@maersk.com",
|
||
"profile_dir": "D:/claw-data/10032/account-manager/profiles/logistics/maersk/Maersk_simulator_booking_demo@maersk.com",
|
||
"url": "http://localhost:5180/industries/logistics/maersk/login",
|
||
"tenant_id": "tenant-demo",
|
||
"environment": "simulator",
|
||
"role": "booking",
|
||
"provider_code": "maersk",
|
||
"session_status": "unknown",
|
||
"auth_strategy": "qr_code_manual",
|
||
"session_persistent": 1,
|
||
"device_fingerprint": null,
|
||
"lease_token": "…………",
|
||
"lease_expires_at": "2026-05-11T12:34:56"
|
||
}
|
||
```
|
||
|
||
`lease_*` 仅在传入 `--lease` 时出现;时间与仓库 `_unix_to_iso_local` 一致。
|
||
|
||
### `account get`
|
||
|
||
输出即数据库映射字典(字段与 [`SCHEMA.md`](SCHEMA.md) `accounts` 一致,`extra_json` 等为结构化 JSON)。
|
||
|
||
### `credential pick`
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"id": 1,
|
||
"platform_key": "deepseek",
|
||
"credential_label": "DeepSeek production key",
|
||
"credential_type": "api_key",
|
||
"secret_storage": "env",
|
||
"secret_ref": "DEEPSEEK_API_KEY",
|
||
"secret_mask": "****7890",
|
||
"status": "active"
|
||
}
|
||
```
|
||
|
||
仅 `--reveal` 时附加 **`secret`**。请勿向终端用户打印 reveal 结果。
|
||
|
||
### `account get-credential`
|
||
|
||
成功示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"account_id": 42,
|
||
"credential_id": 7,
|
||
"credential_type": "password",
|
||
"secret_storage": "local_encrypted",
|
||
"secret_mask": "****abcd",
|
||
"plaintext": "……"
|
||
}
|
||
```
|
||
|
||
失败时使用统一 JSON:`{"success": false, "error": {"code": "...", "message": "..."}}`(亦可能夹杂历史 `ERROR:` 文本命令——见 [`ERRORS.md`](ERRORS.md))。
|
||
|
||
## rpa_helpers 同进程使用(摘要)
|
||
|
||
```python
|
||
import sys
|
||
|
||
AM_ROOT = r"D:\OpenClaw\client-commons\account-manager"
|
||
sys.path.insert(0, f"{AM_ROOT}/scripts")
|
||
|
||
from service.rpa_helpers import (
|
||
launch_browser_with_profile,
|
||
close_browser,
|
||
ensure_logged_in,
|
||
)
|
||
```
|
||
|
||
**务必**:`sys.path` 指向 **`account-manager/scripts`**,因此 import 形态为 **`from service.rpa_helpers ...`**(而不是 `from scripts.service.rpa_helpers ...`)。
|
||
|
||
完整 API、登录判定 hook、`LoginResult` / `AuthStrategyError` 说明见 [`RPA_HELPERS.md`](RPA_HELPERS.md)。
|
||
|
||
## 业务流程示例
|
||
|
||
### 物流 RPA(CLI)
|
||
|
||
1. `account pick-web --platform maersk --environment simulator --role booking --lease --holder my-skill`
|
||
2. 解析 JSON → 取 `id`、`profile_dir`、`url`、`lease_token`
|
||
3. 用 `profile_dir` 启动持久化浏览器上下文
|
||
4. 业务完成后:`lease release <lease_token>`
|
||
5. 如遇登录失效:`account mark-session <id> --session-status needs_login`
|
||
|
||
### LLM API 调用(CLI)
|
||
|
||
1. `credential pick --platform deepseek --type api_key --environment production --reveal`
|
||
2. 解析 JSON → 取 `secret` 字段
|
||
3. 仅在内存中使用,不写磁盘、不打日志
|
||
|
||
### 加密密码 + 自动登录(CLI + rpa_helpers)
|
||
|
||
```python
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
|
||
AM = r"D:\OpenClaw\client-commons\account-manager"
|
||
PY = sys.executable
|
||
MAIN = f"{AM}/scripts/main.py"
|
||
|
||
sys.path.insert(0, f"{AM}/scripts")
|
||
from service.rpa_helpers import launch_browser_with_profile, close_browser, ensure_logged_in
|
||
|
||
|
||
def am_json(argv: list[str]) -> dict:
|
||
r = subprocess.run([PY, MAIN, *argv], capture_output=True, text=True, check=False)
|
||
line = (r.stdout or "").splitlines()[0] if r.stdout else ""
|
||
return json.loads(line)
|
||
|
||
|
||
# 1) pick 账号 + lease
|
||
acc = am_json(
|
||
[
|
||
"account",
|
||
"pick-web",
|
||
"--platform",
|
||
"icbc_sim",
|
||
"--lease",
|
||
"--holder",
|
||
"my-rpa",
|
||
]
|
||
)
|
||
|
||
# 2) 取明文密码(必须 reveal + lease)
|
||
cred = am_json(
|
||
[
|
||
"account",
|
||
"get-credential",
|
||
str(acc["id"]),
|
||
"--reveal",
|
||
"--lease-token",
|
||
acc["lease_token"],
|
||
]
|
||
)
|
||
|
||
# 3) 浏览器 + 登录
|
||
ctx, page = launch_browser_with_profile(acc["profile_dir"])
|
||
try:
|
||
result = ensure_logged_in(page, acc, credentials={"plaintext": cred["plaintext"]})
|
||
if not result.ok:
|
||
print(result.message, file=sys.stderr)
|
||
sys.exit(1)
|
||
# …业务逻辑…
|
||
finally:
|
||
close_browser(ctx)
|
||
subprocess.run([PY, MAIN, "lease", "release", acc["lease_token"]], check=False)
|
||
```
|
||
|
||
## 退出码
|
||
|
||
请以 **stdout JSON / `ERROR:` 前缀** 为准;不要仅依赖退出码。
|