Compare commits

...

2 Commits

Author SHA1 Message Date
718aeefd44 fix: unify row open labels and silent success
All checks were successful
技能自动化发布 / release (push) Successful in 5s
Rename 打开文件位置 to 打开目录, and print empty SAS message so the host skips 执行成功 after opening Explorer or the browser.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 13:15:26 +08:00
123195fcd9 fix: align add-web form fields to column names for lookup
All checks were successful
技能自动化发布 / release (push) Successful in 7s
2026-07-31 15:14:31 +08:00
8 changed files with 86 additions and 22 deletions

View File

@@ -1,7 +1,7 @@
--- ---
name: 账号与凭据管理 name: 账号与凭据管理
description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。" description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。"
version: 2.0.19 version: 2.0.21
author: 深圳匠厂科技有限公司 author: 深圳匠厂科技有限公司
metadata: metadata:
openclaw: openclaw:
@@ -34,8 +34,8 @@ allowed-tools:
5. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。 5. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。
6. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。 6. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。
7. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。 7. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。
8. **浏览器打开查看**:`account open <id>`(或兼容别名 `open <id>`)— **本进程**直接拉起系统 Chrome/Edge(`--user-data-dir`),与「打开文件位置」同模式;不做登录判定。数据管理行内按钮均为 `interaction: direct` + `sync`。 8. **打开浏览器**:`account open <id>`(或兼容别名 `open <id>`)— **本进程**直接拉起系统 Chrome/Edge(`--user-data-dir`),与「打开目录」同模式;不做登录判定。数据管理行内按钮均为 `interaction: direct` + `sync`,成功不弹提示。
9. **打开 Profile 目录**:`account reveal-profile <id>` — 在系统文件管理器中打开该账号 `profile_dir`。 9. **打开目录**:`account reveal-profile <id>` — 在系统文件管理器中打开该账号 `profile_dir`。
10. **管理 lease**:`lease release/list/cleanup` — 防止 RPA 并发。 10. **管理 lease**:`lease release/list/cleanup` — 防止 RPA 并发。
11. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。 11. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。
12. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。 12. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。

View File

@@ -1,6 +1,6 @@
# assets # assets
- `actions.json`:匠厂宿主 Skill Action 清单(数据管理「账号信息」行内:打开文件位置 / 打开浏览器)。 - `actions.json`:匠厂宿主 Skill Action 清单(数据管理「账号信息」行内:打开目录 / 打开浏览器)。
- `examples/`:CLI 成功输出形状示例(虚构路径与数据)。 - `examples/`:CLI 成功输出形状示例(虚构路径与数据)。
- `schemas/`:`get` / `list-json` / `pick-web` 单条对象的轻量 JSON Schema(`additionalProperties: true` 允许扩展字段)。 - `schemas/`:`get` / `list-json` / `pick-web` 单条对象的轻量 JSON Schema(`additionalProperties: true` 允许扩展字段)。

View File

@@ -15,27 +15,27 @@
}, },
"inputSchema": { "inputSchema": {
"type": "object", "type": "object",
"required": ["platform", "accountLabel"], "required": ["platform_key", "account_label"],
"properties": { "properties": {
"platform": { "platform_key": {
"type": "string", "type": "string",
"title": "平台", "title": "平台",
"description": "平台键或显示名,如 cosco / 抖音" "description": "关联平台信息表;宿主按列 lookup 渲染下拉"
}, },
"accountLabel": { "account_label": {
"type": "string", "type": "string",
"title": "账号名称" "title": "账号名称"
}, },
"loginId": { "login_id": {
"type": "string", "type": "string",
"title": "登录账号" "title": "登录账号"
}, },
"loginIdType": { "login_id_type": {
"type": "string", "type": "string",
"title": "登录标识类型", "title": "登录标识类型",
"default": "unknown" "default": "unknown"
}, },
"tenantId": { "tenant_id": {
"type": "string", "type": "string",
"title": "租户编号" "title": "租户编号"
}, },
@@ -49,7 +49,7 @@
"title": "角色", "title": "角色",
"default": "default" "default": "default"
}, },
"providerCode": { "provider_code": {
"type": "string", "type": "string",
"title": "供应商代码" "title": "供应商代码"
}, },
@@ -64,21 +64,21 @@
"command": "account", "command": "account",
"args": [ "args": [
"add-web", "add-web",
"--platform", "{{platform}}", "--platform", "{{platform_key}}",
"--label", "{{accountLabel}}", "--label", "{{account_label}}",
"--login-id", "{{loginId}}", "--login-id", "{{login_id}}",
"--login-id-type", "{{loginIdType}}", "--login-id-type", "{{login_id_type}}",
"--tenant-id", "{{tenantId}}", "--tenant-id", "{{tenant_id}}",
"--environment", "{{environment}}", "--environment", "{{environment}}",
"--role", "{{role}}", "--role", "{{role}}",
"--provider-code", "{{providerCode}}", "--provider-code", "{{provider_code}}",
"--url", "{{url}}" "--url", "{{url}}"
] ]
} }
}, },
{ {
"id": "reveal-profile", "id": "reveal-profile",
"label": "打开文件位置", "label": "打开目录",
"description": "在系统文件管理器中打开该账号的浏览器 Profile 目录。", "description": "在系统文件管理器中打开该账号的浏览器 Profile 目录。",
"placements": ["row"], "placements": ["row"],
"executionProfile": "sync", "executionProfile": "sync",
@@ -108,7 +108,7 @@
{ {
"id": "open-browser", "id": "open-browser",
"label": "打开浏览器", "label": "打开浏览器",
"description": "用该账号持久化 Profile 直接打开系统 Chrome/Edge,仅供肉眼核对(不做登录判定)。与打开文件位置同为一点即开。", "description": "用该账号持久化 Profile 直接打开系统 Chrome/Edge,仅供肉眼核对(不做登录判定)。与打开目录同为一点即开。",
"placements": ["row"], "placements": ["row"],
"executionProfile": "sync", "executionProfile": "sync",
"interaction": "direct", "interaction": "direct",

View File

@@ -75,8 +75,8 @@
| `lease release <lease_token>` | 释放租约 | | `lease release <lease_token>` | 释放租约 |
| `lease list [--active]` | 列出租约 | | `lease list [--active]` | 列出租约 |
| `lease cleanup` | 清理过期租约 | | `lease cleanup` | 清理过期租约 |
| `account open <id>` / `open <id>` | 浏览器查看 | | `account open <id>` / `open <id>` | 打开浏览器 |
| `account reveal-profile <id>` | 打开 Profile 目录 | | `account reveal-profile <id>` | 打开目录 |
### 兼容入口(旧版别名) ### 兼容入口(旧版别名)

View File

@@ -147,6 +147,10 @@ def cmd_reveal_profile(account_id):
print( print(
json.dumps( json.dumps(
{ {
"type": "result",
"schema_version": 1,
"status": "success",
"message": "",
"success": True, "success": True,
"data": { "data": {
"account_id": int(target["id"]) if target.get("id") is not None else account_id, "account_id": int(target["id"]) if target.get("id") is not None else account_id,
@@ -245,6 +249,10 @@ def cmd_open(account_id):
print( print(
json.dumps( json.dumps(
{ {
"type": "result",
"schema_version": 1,
"status": "success",
"message": "",
"success": True, "success": True,
"data": { "data": {
"account_id": int(target["id"]) if target.get("id") is not None else account_id, "account_id": int(target["id"]) if target.get("id") is not None else account_id,

View File

@@ -70,6 +70,9 @@ class TestCmdOpenDirectBrowser:
payload = json.loads(out.splitlines()[-1]) payload = json.loads(out.splitlines()[-1])
assert payload["success"] is True assert payload["success"] is True
assert payload["message"] == ""
assert payload["type"] == "result"
assert payload["status"] == "success"
assert payload["data"]["pid"] == 5252 assert payload["data"]["pid"] == 5252
assert payload["data"]["browser_exe"] == fake_exe assert payload["data"]["browser_exe"] == fake_exe
assert spawned["cmd"][0] == fake_exe assert spawned["cmd"][0] == fake_exe

View File

@@ -54,6 +54,9 @@ def test_reveal_profile_opens_dir(clean_db, tmp_path, capsys):
out = capsys.readouterr().out.strip() out = capsys.readouterr().out.strip()
payload = json.loads(out.splitlines()[-1]) payload = json.loads(out.splitlines()[-1])
assert payload["success"] is True assert payload["success"] is True
assert payload["message"] == ""
assert payload["type"] == "result"
assert payload["status"] == "success"
assert payload["data"]["account_id"] == aid assert payload["data"]["account_id"] == aid
assert payload["data"]["profile_dir"] == called_path assert payload["data"]["profile_dir"] == called_path

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""数据管理行内打开类:label 对齐模板,不与宿主 CRUD 重复。"""
from __future__ import annotations
import json
import os
import unittest
def _skill_root() -> str:
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def _load_actions() -> list[dict]:
path = os.path.join(_skill_root(), "assets", "actions.json")
with open(path, encoding="utf-8") as f:
manifest = json.load(f)
return list(manifest.get("actions") or [])
def _by_id(actions: list[dict], action_id: str) -> dict:
for item in actions:
if item.get("id") == action_id:
return item
raise AssertionError(f"missing action {action_id}")
class TestRowOpenActions(unittest.TestCase):
def test_reveal_and_open_use_template_labels(self) -> None:
actions = _load_actions()
reveal = _by_id(actions, "reveal-profile")
open_browser = _by_id(actions, "open-browser")
self.assertEqual(reveal.get("label"), "打开目录")
self.assertEqual(open_browser.get("label"), "打开浏览器")
self.assertEqual(reveal.get("placements"), ["row"])
self.assertEqual(open_browser.get("placements"), ["row"])
self.assertEqual(reveal.get("interaction"), "direct")
self.assertEqual(open_browser.get("interaction"), "direct")
self.assertFalse(reveal.get("replaces"))
self.assertFalse(open_browser.get("replaces"))
def test_create_takes_over_host_add_without_row_duplicate(self) -> None:
create = _by_id(_load_actions(), "account-add-web")
self.assertEqual(create.get("replaces"), "create")
self.assertEqual(create.get("placements") or [], [])
self.assertEqual(create.get("label"), "新增账号")
if __name__ == "__main__":
unittest.main()