From cbbec7cdfc17b6b5720415d7f031364efec20b79 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Sun, 26 Jul 2026 16:03:27 +0800 Subject: [PATCH] fix: add init-db CLI for host install/upgrade metadata seed --- SKILL.md | 29 +++++++++---------- references/CLI.md | 6 ++++ scripts/cli/app.py | 8 ++++++ scripts/service/account_service.py | 8 +++++- scripts/service/platform_commands.py | 30 ++++++++++++++++++++ tests/test_usage.py | 42 ++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+), 15 deletions(-) diff --git a/SKILL.md b/SKILL.md index 4a86950..9bcc213 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 账号与凭据管理 description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。" -version: 2.0.12 +version: 2.0.13 author: 深圳匠厂科技有限公司 metadata: openclaw: @@ -27,19 +27,20 @@ allowed-tools: ## 何时使用本技能 -1. **登记网页/RPA 账号**:`account add-web` — 平台 + 登录标识 + 自动生成 profile_dir;可选 `--auth-strategy` / `--session-persistent` / `--device-fingerprint`。 -2. **登记凭据(API Key / Token / 加密密码)**:`account add-secret` — `none` / `env` / `windows_credential` / `local_encrypted`。 -3. **查看/列出账号**:`account get/list` — JSON 输出;list 可按 `--login-id` / `--label` 筛选。 -4. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。 -5. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。 -6. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。 -7. **浏览器打开查看**:`account open `(或兼容别名 `open `)— 仅查看,不做站点侧登录判定。 -8. **管理 lease**:`lease release/list/cleanup` — 防止 RPA 并发。 -9. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。 -10. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。 -11. **按 lease 取明文密码**:`account get-credential --reveal --lease-token ` — 结合 pick-web 返回的 lease 解密 `local_encrypted` 等路径。 -12. **业务 skill 同进程登录**:`from service.rpa_helpers import ensure_logged_in` — 详见 [`references/RPA_HELPERS.md`](references/RPA_HELPERS.md)。 -13. **有则用、无则建(网页账号)**:`account ensure-web --platform

` — 登记账号 ≠ 已登录;登录仍由业务技能 / `ensure_logged_in` 处理。 +1. **安装/升级后初始化库与数据管理元数据**:`init-db`(宿主会自动调用;也可手工执行) +2. **登记网页/RPA 账号**:`account add-web` — 平台 + 登录标识 + 自动生成 profile_dir;可选 `--auth-strategy` / `--session-persistent` / `--device-fingerprint`。 +3. **登记凭据(API Key / Token / 加密密码)**:`account add-secret` — `none` / `env` / `windows_credential` / `local_encrypted`。 +4. **查看/列出账号**:`account get/list` — JSON 输出;list 可按 `--login-id` / `--label` 筛选。 +5. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。 +6. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。 +7. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。 +8. **浏览器打开查看**:`account open `(或兼容别名 `open `)— 仅查看,不做站点侧登录判定。 +9. **管理 lease**:`lease release/list/cleanup` — 防止 RPA 并发。 +10. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。 +11. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。 +12. **按 lease 取明文密码**:`account get-credential --reveal --lease-token ` — 结合 pick-web 返回的 lease 解密 `local_encrypted` 等路径。 +13. **业务 skill 同进程登录**:`from service.rpa_helpers import ensure_logged_in` — 详见 [`references/RPA_HELPERS.md`](references/RPA_HELPERS.md)。 +14. **有则用、无则建(网页账号)**:`account ensure-web --platform

` — 登记账号 ≠ 已登录;登录仍由业务技能 / `ensure_logged_in` 处理。 ## CLI 调用 diff --git a/references/CLI.md b/references/CLI.md index 630e0ec..84d6f2a 100644 --- a/references/CLI.md +++ b/references/CLI.md @@ -17,6 +17,12 @@ ## 子命令速查(按业务流程) +### 0. 运行时初始化(宿主标准) + +| CLI | 说明 | +|---|---| +| `init-db` | 幂等初始化本地 SQLite、schema 迁移与数据管理中文元数据(`_jiangchang_*`)。成功输出 JSON:`{"ok":true,"skill":"account-manager","db_path":"..."}`。**匠厂安装/升级技能时会调用本命令**;业务命令内部也会触发同等 `init_db()`。 | + ### A. 平台管理 | CLI | 说明 | diff --git a/scripts/cli/app.py b/scripts/cli/app.py index 90ed93f..b29de3b 100644 --- a/scripts/cli/app.py +++ b/scripts/cli/app.py @@ -38,6 +38,7 @@ from service.account_service import ( cmd_delete_by_platform, cmd_delete_by_platform_phone, cmd_get, + cmd_init_db, cmd_lease_cleanup, cmd_lease_list, cmd_lease_release, @@ -60,6 +61,9 @@ from util.runtime_paths import _maybe_print_paths_debug_cli _USAGE = """account-manager v2 — Credential & Browser Profile Manager +运行时 / 数据管理: + init-db # 幂等初始化本地库与数据管理中文元数据(宿主安装/升级会调用) + 平台管理: platform list [--domain logistics|llm|content] platform get @@ -217,6 +221,10 @@ def main() -> None: # Determine if first arg is a platform key (for legacy shorthand) plat_from_first = resolve_platform_key(cmd) + # ---- init-db(宿主 install/upgrade 标准入口)---- + if cmd == "init-db": + sys.exit(cmd_init_db()) + # ---- platform subcommand ---- if cmd == "platform": if len(av) < 3: diff --git a/scripts/service/account_service.py b/scripts/service/account_service.py index 6ce57e2..c184d20 100644 --- a/scripts/service/account_service.py +++ b/scripts/service/account_service.py @@ -39,7 +39,12 @@ from service.legacy_commands import ( cmd_list, ) from service.lease_commands import cmd_lease_cleanup, cmd_lease_list, cmd_lease_release -from service.platform_commands import cmd_platform_ensure, cmd_platform_get, cmd_platform_list +from service.platform_commands import ( + cmd_init_db, + cmd_platform_ensure, + cmd_platform_get, + cmd_platform_list, +) __all__ = [ "PlatformCallerMeta", @@ -60,6 +65,7 @@ __all__ = [ "cmd_delete_by_platform", "cmd_delete_by_platform_phone", "cmd_get", + "cmd_init_db", "cmd_lease_cleanup", "cmd_lease_list", "cmd_lease_release", diff --git a/scripts/service/platform_commands.py b/scripts/service/platform_commands.py index 8763a1c..2916f63 100644 --- a/scripts/service/platform_commands.py +++ b/scripts/service/platform_commands.py @@ -6,8 +6,38 @@ from typing import Optional from db.accounts_repo import get_platform_from_db, list_platforms_from_db, upsert_platform from db.connection import get_conn, init_db from service._svc_common import _err_json, _ok_json, _resolve_or_fail, _say +from util.constants import SKILL_SLUG from util.logging_config import get_skill_logger from util.platforms import DEFAULT_CAPABILITIES_JSON, is_valid_platform_key_format, seed_platforms +from util.runtime_paths import get_db_path + + +def cmd_init_db() -> int: + """幂等创建/迁移本地 SQLite 与数据管理元数据(宿主 install/upgrade 可调用)。""" + log = get_skill_logger() + log.info("cli_start subcommand=init_db") + try: + init_db() + except Exception as exc: # noqa: BLE001 — CLI 边界,统一成可机读失败 + log.exception("init_db_failure") + print( + json.dumps( + {"ok": False, "skill": SKILL_SLUG, "error": str(exc)}, + ensure_ascii=False, + ) + ) + return 1 + print( + json.dumps( + { + "ok": True, + "skill": SKILL_SLUG, + "db_path": get_db_path(), + }, + ensure_ascii=False, + ) + ) + return 0 def cmd_platform_list(domain: Optional[str] = None) -> None: diff --git a/tests/test_usage.py b/tests/test_usage.py index 6767a47..b1ca568 100644 --- a/tests/test_usage.py +++ b/tests/test_usage.py @@ -23,6 +23,7 @@ def test_usage_includes_all_account_subcommands(): assert r.returncode == 1 usage = r.stdout for cmd in ( + "init-db", "init-master-key", "export-credentials", "import-credentials", @@ -35,3 +36,44 @@ def test_usage_includes_all_account_subcommands(): assert cmd in usage, f"USAGE 缺少 {cmd}" assert "--ensure" in usage assert "--login-id" in usage + + +def test_init_db_cli_succeeds(fake_env): + """宿主调用的 init-db 必须成功并写入展示元数据。""" + import json + + env = { + **os.environ, + "PYTHONPATH": _scripts, + "JIANGCHANG_DATA_ROOT": fake_env["data_root"], + "JIANGCHANG_USER_ID": fake_env["user_id"], + } + r = subprocess.run( + [sys.executable, "main.py", "init-db"], + capture_output=True, + text=True, + env=env, + cwd=_scripts, + ) + assert r.returncode == 0, r.stdout + r.stderr + payload = json.loads(r.stdout.strip().splitlines()[-1]) + assert payload.get("ok") is True + assert payload.get("skill") == "account-manager" + assert payload.get("db_path") + + from db.connection import get_conn + + conn = get_conn() + try: + cur = conn.cursor() + cur.execute("SELECT COUNT(*) FROM _jiangchang_columns") + assert int(cur.fetchone()[0]) > 0 + cur.execute( + """ + SELECT display_name FROM _jiangchang_columns + WHERE table_name = 'accounts' AND column_name = 'role' + """ + ) + assert cur.fetchone()[0] == "角色" + finally: + conn.close()