diff --git a/scripts/cli/app.py b/scripts/cli/app.py index 6c3abb0..57f1e2e 100644 --- a/scripts/cli/app.py +++ b/scripts/cli/app.py @@ -3,7 +3,7 @@ CLI entry point: argv dispatch for account-manager v2. Subcommand tree: - platform list/get + platform list/get/ensure account add-web|add-secret|get|get-credential|list|pick-web|mark-session|mark-error|mark-used|open init-master-key|export-credentials|import-credentials credential pick|resolve @@ -42,6 +42,7 @@ from service.account_service import ( cmd_list, cmd_list_json, cmd_pick_web, + cmd_platform_ensure, cmd_platform_get, cmd_platform_list, ) @@ -60,6 +61,8 @@ _USAGE = """account-manager v2 — Credential & Browser Profile Manager 平台管理: platform list [--domain logistics|llm|content] platform get + platform ensure --key --display-name [--domain ] + [--url ] [--auth-strategy ] 账号管理: account add-web --platform

--login-id --login-id-type @@ -161,7 +164,7 @@ def main() -> None: # ---- platform subcommand ---- if cmd == "platform": if len(av) < 3: - print("ERROR:CLI_BAD_ARGS platform 需要子命令:list | get") + print("ERROR:CLI_BAD_ARGS platform 需要子命令:list | get | ensure") sys.exit(1) sub = av[2] if sub == "list": @@ -172,8 +175,30 @@ def main() -> None: print("ERROR:CLI_BAD_ARGS platform get 需要 。") sys.exit(1) cmd_platform_get(av[3]) + elif sub == "ensure": + import argparse as _ap + p = _ap.ArgumentParser(prog="platform ensure") + p.add_argument("--key", required=True) + p.add_argument("--display-name", default="") + p.add_argument("--domain", default="generic") + p.add_argument("--url", default="") + p.add_argument("--auth-strategy", default=None) + p.add_argument("--aliases", default=None, + help="JSON 数组字符串,如 '[\"1688\",\"阿里巴巴1688\"]'") + p.add_argument("--capabilities", default=None, + help='JSON 对象,如 \'{"web":true,"api_key":false,"rpa":true}\'') + args = p.parse_args(av[3:]) + sys.exit(cmd_platform_ensure( + key=args.key, + display_name=args.display_name or args.key, + domain=args.domain, + url=args.url, + auth_strategy=args.auth_strategy, + aliases=args.aliases, + capabilities=args.capabilities, + )) else: - print("ERROR:CLI_BAD_ARGS platform 子命令必须是 list 或 get。") + print("ERROR:CLI_BAD_ARGS platform 子命令必须是 list、get 或 ensure。") sys.exit(1) # ---- account subcommand ---- diff --git a/scripts/db/accounts_repo.py b/scripts/db/accounts_repo.py index 84c90be..a4d0170 100644 --- a/scripts/db/accounts_repo.py +++ b/scripts/db/accounts_repo.py @@ -763,6 +763,49 @@ def get_active_lease_by_token(conn, lease_token: str) -> Optional[dict[str, Any] } +def upsert_platform( + conn, + key: str, + display_name: str, + domain: str = "generic", + provider_code: str | None = None, + default_url: str = "", + aliases_json: str = "[]", + capabilities_json: str = '{"web": true, "api_key": false, "rpa": true}', + default_auth_strategy: str | None = None, +) -> None: + """幂等地注册或更新平台到 DB。""" + now = int(time.time()) + if provider_code is None: + provider_code = key + conn.execute( + """ + INSERT INTO platforms ( + platform_key, display_name, domain, provider_code, + default_url, aliases_json, capabilities_json, + enabled, default_auth_strategy, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?) + ON CONFLICT(platform_key) DO UPDATE SET + display_name = excluded.display_name, + domain = excluded.domain, + provider_code = excluded.provider_code, + default_url = excluded.default_url, + aliases_json = excluded.aliases_json, + capabilities_json = excluded.capabilities_json, + enabled = 1, + default_auth_strategy = COALESCE(excluded.default_auth_strategy, + platforms.default_auth_strategy), + updated_at = excluded.updated_at + """, + ( + key, display_name, domain, provider_code, + default_url, aliases_json, capabilities_json, + default_auth_strategy, now, now, + ), + ) + conn.commit() + + # ============================================================================ # Internal helpers # ============================================================================ diff --git a/scripts/service/account_service.py b/scripts/service/account_service.py index 5d881b0..5f92817 100644 --- a/scripts/service/account_service.py +++ b/scripts/service/account_service.py @@ -42,6 +42,7 @@ from db.accounts_repo import ( update_account_last_used, update_account_session_status, update_credential_last_used, + upsert_platform, ) from db.auth_strategy import ( ALL_AUTH_STRATEGIES, @@ -190,15 +191,49 @@ def _remove_profile_dir(path: str) -> None: def _resolve_or_fail(input_name: str, hint: str = "") -> Optional[str]: """Resolve platform key or print ERROR:INVALID_PLATFORM and return None.""" - key = resolve_platform_key((input_name or "").strip()) - if not key: - _say(f"ERROR:INVALID_PLATFORM 无法识别的平台「{input_name}」。") - if hint: - _say(hint) - else: - _say("支持:" + _platform_list_cn_for_help()) + raw = (input_name or "").strip() + if not raw: + _say("ERROR:INVALID_PLATFORM 平台名不能为空。") return None - return key + + # 先尝试 platforms.py 硬编码(兼容历史) + key = resolve_platform_key(raw) + if key: + return key + + # 再查 DB(支持动态注册的平台) + try: + init_db() + conn = get_conn() + try: + row = conn.execute( + "SELECT platform_key FROM platforms WHERE platform_key = ? AND enabled = 1", + (raw,), + ).fetchone() + if row: + return row[0] + rows = conn.execute( + "SELECT platform_key, aliases_json FROM platforms WHERE enabled = 1" + ).fetchall() + raw_lower = raw.lower() + for r in rows: + try: + aliases = json.loads(r[1] or "[]") + if raw_lower in [str(a).lower() for a in aliases]: + return r[0] + except Exception: + continue + finally: + conn.close() + except Exception: + pass + + _say(f"ERROR:INVALID_PLATFORM 无法识别的平台「{input_name}」。") + if hint: + _say(hint) + else: + _say("支持:" + _platform_list_cn_for_help()) + return None # ============================================================================ @@ -238,6 +273,55 @@ def cmd_platform_get(input_key: str) -> None: _say(_err_json("ERROR:INVALID_PLATFORM", f"Platform '{key}' not found in DB.")) +def cmd_platform_ensure( + key: str, + display_name: str, + domain: str = "generic", + url: str = "", + auth_strategy: Optional[str] = None, + aliases: Optional[str] = None, + capabilities: Optional[str] = None, +) -> int: + """ + 幂等地注册或更新平台到 DB。 + 输出单行 JSON:{"ok": true, "platform_key": "xxx", "action": "created|updated"} + """ + log = get_skill_logger() + key = (key or "").strip() + display_name = (display_name or key).strip() + if not key: + _say('ERROR:INVALID_ARGS platform ensure 需要 --key 参数') + return 1 + + aliases_json = aliases if aliases else json.dumps([key, display_name], ensure_ascii=False) + capabilities_json = capabilities if capabilities else '{"web": true, "api_key": false, "rpa": true}' + + init_db() + conn = get_conn() + try: + existed = bool( + conn.execute( + "SELECT 1 FROM platforms WHERE platform_key = ?", (key,) + ).fetchone() + ) + upsert_platform( + conn, + key=key, + display_name=display_name, + domain=domain, + default_url=url, + aliases_json=aliases_json, + capabilities_json=capabilities_json, + default_auth_strategy=auth_strategy, + ) + finally: + conn.close() + action = "updated" if existed else "created" + log.info("platform_ensure key=%s action=%s", key, action) + _say(json.dumps({"ok": True, "platform_key": key, "action": action}, ensure_ascii=False)) + return 0 + + # ============================================================================ # Account add-web # ============================================================================