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,325 @@
# -*- coding: utf-8 -*-
"""Account query and pick CLI commands."""
import json
import uuid
from typing import Optional
from db.accounts_repo import (
acquire_lease,
find_account_ids,
find_accounts,
find_credentials,
get_account_by_id,
get_active_lease_by_token,
update_account_last_used,
)
from db.connection import get_conn, init_db
from db.credentials_repo import (
CredentialNotEncryptedError,
list_credentials_by_account,
read_credential_plaintext,
)
from service._svc_common import (
PlatformCallerMeta,
_err_json,
_resolve_or_auto_register,
_resolve_or_fail,
_say,
_say_err,
)
from service.crypto import CredentialDecryptError
from service.master_key import MasterKeyMissingError
from util.logging_config import get_skill_logger
from util.platforms import seed_platforms
from util.runtime_paths import _runtime_paths_debug_text
def cmd_account_get(account_id, include_credentials: bool = False) -> None:
"""Get a single account by id. Returns JSON."""
log = get_skill_logger()
log.info("cli_start subcommand=account_get id=%r", account_id)
init_db()
acc = get_account_by_id(account_id)
if not acc:
log.warning("account_get_not_found id=%r", account_id)
_say(f"ERROR:ACCOUNT_NOT_FOUND 账号 id={account_id} 不存在。")
_say_err(_runtime_paths_debug_text())
return
if include_credentials:
creds = find_credentials(
platform_key=acc["platform_key"],
limit=20,
)
linked_creds = [c for c in creds if c["account_id"] == acc["id"]]
if not linked_creds:
linked_creds = [c for c in creds if c["account_id"] is None]
acc["credentials"] = [
{
"id": c["id"],
"credential_label": c["credential_label"],
"credential_type": c["credential_type"],
"secret_storage": c["secret_storage"],
"secret_ref": c["secret_ref"],
"secret_mask": c["secret_mask"],
"status": c["status"],
}
for c in linked_creds
]
_say(json.dumps(acc, ensure_ascii=False))
def cmd_account_get_credential(
account_id: int,
*,
reveal: bool = False,
lease_token: Optional[str] = None,
) -> None:
"""account get-credential — metadata or plaintext when lease proves access."""
log = get_skill_logger()
log.info("cli_start subcommand=account_get_credential id=%s reveal=%s", account_id, reveal)
init_db()
acc = get_account_by_id(account_id)
if not acc:
log.warning("account_get_credential_not_found id=%s", account_id)
_say(_err_json("ERROR:ACCOUNT_NOT_FOUND", f"账号 id={account_id} 不存在。"))
return
conn = get_conn()
try:
creds_all = list_credentials_by_account(conn, account_id)
creds = [c for c in creds_all if (c.get("status") or "").strip().lower() == "active"]
if not creds:
_say(_err_json(
"ERR_CREDENTIAL_MISSING",
f"账号 id={account_id} 没有活跃凭据。",
))
return
cred = creds[0]
base_out = {
"success": True,
"account_id": account_id,
"credential_id": cred["id"],
"credential_type": cred["credential_type"],
"secret_storage": cred["secret_storage"],
"secret_mask": cred["secret_mask"],
}
if not reveal:
_say(json.dumps(base_out, ensure_ascii=False))
return
tok = (lease_token or "").strip()
if not tok:
log.warning("account_get_credential_reveal_missing_token id=%s", account_id)
_say(_err_json(
"ERR_LEASE_INVALID",
"reveal 必须带 --lease-token。",
))
return
lease = get_active_lease_by_token(conn, tok)
lease_prefix = tok[:8]
if lease is None:
log.warning(
"account_get_credential_reveal_bad_lease id=%s lease_prefix=%s",
account_id,
lease_prefix,
)
_say(_err_json(
"ERR_LEASE_INVALID",
"lease token 无效或已过期",
))
return
if int(lease["account_id"]) != int(account_id):
log.warning(
"account_get_credential_reveal_lease_mismatch id=%s lease_account=%s lease_prefix=%s",
account_id,
lease["account_id"],
lease_prefix,
)
_say(_err_json(
"ERR_LEASE_INVALID",
"lease token 不属于该 account",
))
return
try:
pt = read_credential_plaintext(conn, int(cred["id"]))
except MasterKeyMissingError as e:
log.warning("account_get_credential_master_missing id=%s", account_id)
_say(_err_json("ERR_MASTER_KEY_MISSING", str(e)))
return
except CredentialDecryptError as e:
log.warning(
"account_get_credential_decrypt_failed id=%s cred_id=%s lease_prefix=%s",
account_id,
cred["id"],
lease_prefix,
)
_say(_err_json("ERR_CREDENTIAL_DECRYPT_FAILED", str(e)))
return
except CredentialNotEncryptedError:
out = {**base_out, "plaintext": None, "note": "credential storage=none, no plaintext to reveal"}
log.info(
"account_get_credential_reveal_note_plain id=%s cred_id=%s lease_prefix=%s",
account_id,
cred["id"],
lease_prefix,
)
_say(json.dumps(out, ensure_ascii=False))
return
log.info(
"account_get_credential_reveal_success account_id=%s cred_id=%s lease_prefix=%s",
account_id,
cred["id"],
lease_prefix,
)
_say(json.dumps({**base_out, "plaintext": pt}, ensure_ascii=False))
finally:
conn.close()
def cmd_get(account_id) -> None:
"""Legacy: get <id> — same as account get <id>."""
cmd_account_get(account_id, include_credentials=False)
def cmd_account_list(
platform_input: Optional[str] = None,
environment: Optional[str] = None,
tenant_id: Optional[str] = None,
role: Optional[str] = None,
limit: int = 200,
) -> None:
"""account list — output JSON array of accounts matching filters."""
log = get_skill_logger()
log.info("cli_start subcommand=account_list platform=%s env=%s tenant=%s role=%s",
platform_input, environment, tenant_id, role)
init_db()
key = None
if platform_input and platform_input not in ("all", "全部", ""):
key = _resolve_or_fail(platform_input)
if not key:
return
accounts = find_accounts(
platform_key=key,
environment=environment,
tenant_id=tenant_id,
role=role,
limit=limit,
)
_say(json.dumps(accounts, ensure_ascii=False))
def cmd_list_json(platform_input: str, limit: int = 200) -> None:
"""Legacy: list-json <platform|all> [--limit N]"""
log = get_skill_logger()
log.info("cli_start subcommand=list_json filter=%r limit=%s", platform_input, limit)
init_db()
raw = (platform_input or "all").strip()
if not raw or raw.lower() == "all" or raw == "全部":
key = None
else:
key = _resolve_or_fail(raw)
if not key:
return
lim = max(1, min(int(limit), 500))
accounts = find_accounts(platform_key=key, limit=lim)
_say(json.dumps(accounts, ensure_ascii=False))
def cmd_account_pick_web(
platform_input: str,
environment: Optional[str] = None,
tenant_id: Optional[str] = None,
role: Optional[str] = None,
do_lease: bool = False,
ttl_sec: int = 900,
holder: Optional[str] = None,
purpose: Optional[str] = None,
platform_meta: Optional[PlatformCallerMeta] = None,
) -> None:
"""Pick a web/RPA account for use. Optionally acquire a lease."""
log = get_skill_logger()
log.info("account_pick_web_attempt platform=%s env=%s tenant=%s role=%s lease=%s",
platform_input, environment, tenant_id, role, do_lease)
key = _resolve_or_auto_register(platform_input, platform_meta)
if not key:
return
init_db()
conn = get_conn()
try:
seed_platforms(conn)
finally:
conn.close()
candidate_ids = find_account_ids(
platform_key=key,
environment=environment,
tenant_id=tenant_id,
role=role,
)
if not candidate_ids:
_say(_err_json("ERROR:NO_ACCOUNT",
"没有符合条件的可用账号status=active 且未被其他 lease 占用)。"))
return
picked_id = candidate_ids[0]
acc = get_account_by_id(picked_id)
if not acc:
_say(_err_json("ERROR:ACCOUNT_NOT_FOUND", "选中的账号突然不存在了。"))
return
update_account_last_used(picked_id)
result = {
"id": acc["id"],
"platform_key": acc["platform_key"],
"account_label": acc["account_label"],
"login_id": acc["login_id"],
"profile_dir": acc["profile_dir"],
"url": acc["url"],
"tenant_id": acc["tenant_id"],
"environment": acc["environment"],
"role": acc["role"],
"provider_code": acc["provider_code"],
"session_status": acc["session_status"],
"auth_strategy": acc["auth_strategy"],
"session_persistent": acc["session_persistent"],
"device_fingerprint": acc.get("device_fingerprint"),
}
lease_info = None
if do_lease:
lease_token = uuid.uuid4().hex[:32]
resolved_holder = holder or "unknown"
try:
lease_info = acquire_lease(
account_id=picked_id,
lease_token=lease_token,
holder=resolved_holder,
purpose=purpose,
ttl_sec=ttl_sec,
)
result["lease_token"] = lease_info["lease_token"]
result["lease_expires_at"] = lease_info["expires_at"]
except ValueError as e:
log.warning("account_pick_web_lease_conflict id=%s", picked_id)
_say(_err_json("ERROR:LEASE_CONFLICT", str(e)))
return
log.info("account_pick_web_success id=%s token_prefix=%s",
picked_id, lease_info["lease_token"][:8] if lease_info else "none")
_say(json.dumps(result, ensure_ascii=False))
def cmd_pick_web(platform_input: str) -> None:
"""Legacy: pick-web <platform> — same as account pick-web without lease."""
cmd_account_pick_web(platform_input, do_lease=False)