chore: auto release commit (2026-06-19 11:19:10)
All checks were successful
技能自动化发布 / release (push) Successful in 5s

This commit is contained in:
2026-06-19 11:19:10 +08:00
parent 75c7ea67d5
commit 71bef19514
9 changed files with 1607 additions and 1530 deletions

View File

@@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
"""Shared helpers for account service command modules."""
import json
import os
import shutil
import sys
from dataclasses import dataclass, field
from typing import Optional
from db.accounts_repo import (
insert_account,
resolve_auth_strategy_for_platform,
ensure_platform_from_caller_meta,
)
from db.auth_strategy import (
ALL_AUTH_STRATEGIES,
AUTH_STRATEGY_CLIENT_CERTIFICATE,
AUTH_STRATEGY_DEVICE_BOUND,
AUTH_STRATEGY_PASSWORD_PLUS_2FA,
AUTH_STRATEGY_PER_SESSION_MANUAL,
AUTH_STRATEGY_QR_CODE_MANUAL,
AUTH_STRATEGY_SSO_REDIRECT,
)
from db.connection import get_conn, init_db
from util.logging_config import get_skill_logger
from util.platforms import (
DEFAULT_CAPABILITIES_JSON,
PlatformResolveResult,
_platform_list_cn_for_help,
is_valid_platform_key_format,
resolve_platform_key_dynamic,
seed_platforms,
)
def _map_secret_read_error(exc: BaseException) -> tuple[str, str]:
"""Map secret read/write exceptions to ERROR:* codes (no secret text in code)."""
msg = str(exc)
if "SECRET_ENV_MISSING" in msg:
return "ERROR:SECRET_ENV_MISSING", msg
if "WINDOWS_CREDENTIAL_UNAVAILABLE" in msg:
return "ERROR:WINDOWS_CREDENTIAL_UNAVAILABLE", msg
return "ERROR:SECRET_READ_FAILED", msg
def _credential_ok_line(payload: dict) -> str:
"""Single-line JSON for credential commands (machine integration)."""
return json.dumps({"success": True, **payload}, ensure_ascii=False)
def _derive_session_persistent(auth_strategy: str) -> int:
"""Derive default session_persistent from auth_strategy when CLI omits it."""
if auth_strategy in (
AUTH_STRATEGY_QR_CODE_MANUAL,
AUTH_STRATEGY_PASSWORD_PLUS_2FA,
AUTH_STRATEGY_DEVICE_BOUND,
AUTH_STRATEGY_SSO_REDIRECT,
):
return 1
if auth_strategy in (AUTH_STRATEGY_PER_SESSION_MANUAL, AUTH_STRATEGY_CLIENT_CERTIFICATE):
return 0
return 1
def _insert_secret_holder_account(
conn,
*,
platform_key: str,
account_label: str,
environment: str,
role: str,
provider_code: str,
url: Optional[str],
now: int,
) -> int:
"""Create a minimal accounts row so credentials can join on environment/role."""
return insert_account(
conn=conn,
platform_key=platform_key,
account_label=account_label,
login_id=None,
login_id_type="api_account",
tenant_id=None,
environment=environment,
role=role,
provider_code=provider_code,
profile_dir=None,
url=url,
extra_json="{}",
now=now,
auth_strategy=resolve_auth_strategy_for_platform(conn, platform_key),
)
def _err_json(code: str, message: str) -> str:
"""Return a JSON error line."""
return json.dumps(
{"success": False, "error": {"code": code, "message": message}},
ensure_ascii=False,
)
def _ok_json(data) -> str:
"""Return a JSON success line."""
return json.dumps(
{"success": True, "data": data},
ensure_ascii=False,
)
def _say(msg: str) -> None:
"""Print to stdout."""
print(msg)
def _say_err(msg: str) -> None:
"""Print to stderr."""
print(msg, file=sys.stderr)
def _fail_human(code: str, hint_lines: list[str]) -> None:
"""Print human-readable error block."""
_say(f"ERROR:{code}")
for line in hint_lines:
_say(line)
def _remove_profile_dir(path: str) -> None:
p = (path or "").strip()
if not p or not os.path.isdir(p):
return
try:
shutil.rmtree(p)
except OSError as e:
_say_err(f"WARNING: 未能删除用户数据目录(可手工删):{p}\n {e}")
@dataclass
class PlatformCallerMeta:
"""调用方为未注册自定义平台提供的自动注册元数据pick-web / add-web"""
display_name: Optional[str] = None
url: Optional[str] = None
domain: Optional[str] = None
auth_strategy: Optional[str] = None
aliases: list[str] = field(default_factory=list)
def is_sufficient_for_auto_register(self) -> bool:
return bool((self.display_name or "").strip() and (self.url or "").strip())
def _emit_platform_resolve_error(result: PlatformResolveResult, hint: str = "") -> None:
code = result.error_code or "INVALID_PLATFORM"
if not code.startswith("ERROR:"):
code = f"ERROR:{code}"
_say(_err_json(code, result.message or "无法识别平台。"))
if hint:
_say(hint)
def _resolve_or_auto_register(
platform_input: str,
meta: Optional[PlatformCallerMeta] = None,
*,
hint: str = "",
) -> Optional[str]:
"""解析平台;未注册时可在元数据充足时自动 ensure幂等、仅填空字段"""
init_db()
conn = get_conn()
try:
result = resolve_platform_key_dynamic(platform_input, conn=conn)
if result.ok:
return result.platform_key
if result.error_code in ("PLATFORM_DISABLED", "INVALID_PLATFORM_KEY", "INVALID_PLATFORM"):
_emit_platform_resolve_error(result, hint=hint)
return None
if result.error_code != "PLATFORM_NOT_REGISTERED":
_emit_platform_resolve_error(result, hint=hint)
if result.error_code not in (
"PLATFORM_NOT_REGISTERED",
"INVALID_PLATFORM_KEY",
"PLATFORM_DISABLED",
):
_say("支持:" + _platform_list_cn_for_help())
return None
raw_key = (platform_input or "").strip().lower()
if not is_valid_platform_key_format(raw_key):
_emit_platform_resolve_error(
PlatformResolveResult(
error_code="INVALID_PLATFORM_KEY",
message=result.message or f"平台 key 格式非法:「{platform_input}」。",
),
hint=hint,
)
return None
if not meta or not meta.is_sufficient_for_auto_register():
_emit_platform_resolve_error(
PlatformResolveResult(
error_code="PLATFORM_NOT_REGISTERED",
message=(
f"平台 {raw_key} 尚未注册。"
"请传入 --platform-display-name 与 --platform-url"
"或先执行 account platform ensure。"
),
),
hint=hint,
)
return None
if meta.auth_strategy and meta.auth_strategy not in ALL_AUTH_STRATEGIES:
_say(_err_json(
"ERR_AUTH_STRATEGY_NOT_SUPPORTED",
f"不支持的 --platform-auth-strategy{meta.auth_strategy}",
))
return None
seed_platforms(conn)
action = ensure_platform_from_caller_meta(
conn,
raw_key,
display_name=(meta.display_name or "").strip(),
domain=(meta.domain or "generic").strip() or "generic",
default_url=(meta.url or "").strip(),
aliases=meta.aliases,
default_auth_strategy=meta.auth_strategy,
capabilities_json=DEFAULT_CAPABILITIES_JSON,
)
get_skill_logger().info(
"platform_auto_registered key=%s action=%s", raw_key, action
)
return raw_key
finally:
conn.close()
def _resolve_or_fail(input_name: str, hint: str = "") -> Optional[str]:
"""Resolve platform key or emit structured JSON error and return None."""
init_db()
conn = get_conn()
try:
result = resolve_platform_key_dynamic(input_name, conn=conn)
finally:
conn.close()
if result.ok:
return result.platform_key
_emit_platform_resolve_error(result, hint=hint)
if result.error_code not in ("PLATFORM_NOT_REGISTERED", "INVALID_PLATFORM_KEY", "PLATFORM_DISABLED"):
_say("支持:" + _platform_list_cn_for_help())
return None