547 lines
18 KiB
Python
547 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
CLI entry point: argv dispatch for account-manager v2.
|
||
|
||
Subcommand tree:
|
||
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
|
||
lease release|list|cleanup
|
||
(legacy) add|list|get|list-json|pick-web|open|delete
|
||
|
||
Machine commands output single-line JSON where appropriate.
|
||
"""
|
||
import sys
|
||
from typing import Optional
|
||
|
||
from service.account_crypto_commands import (
|
||
cmd_account_export_credentials,
|
||
cmd_account_import_credentials,
|
||
cmd_account_init_master_key,
|
||
)
|
||
from service.account_service import (
|
||
cmd_account_add_secret,
|
||
cmd_account_add_web,
|
||
cmd_account_get,
|
||
cmd_account_get_credential,
|
||
cmd_account_list,
|
||
cmd_account_mark_error,
|
||
cmd_account_mark_session,
|
||
cmd_account_mark_used,
|
||
cmd_account_pick_web,
|
||
cmd_credential_pick,
|
||
cmd_credential_resolve,
|
||
cmd_delete_by_id,
|
||
cmd_delete_by_platform,
|
||
cmd_delete_by_platform_phone,
|
||
cmd_get,
|
||
cmd_lease_cleanup,
|
||
cmd_lease_list,
|
||
cmd_lease_release,
|
||
cmd_list,
|
||
cmd_list_json,
|
||
cmd_pick_web,
|
||
cmd_platform_ensure,
|
||
cmd_platform_get,
|
||
cmd_platform_list,
|
||
)
|
||
from service.browser_service import cmd_open
|
||
from util.constants import LOG_LOGGER_NAME, SKILL_SLUG
|
||
from util.logging_config import get_skill_logger, setup_skill_logging
|
||
from util.platforms import _platform_list_cn_for_help, resolve_platform_key
|
||
from util.runtime_paths import _maybe_print_paths_debug_cli
|
||
|
||
# =========================================================================
|
||
# Help text
|
||
# =========================================================================
|
||
|
||
_USAGE = """account-manager v2 — Credential & Browser Profile Manager
|
||
|
||
平台管理:
|
||
platform list [--domain logistics|llm|content]
|
||
platform get <platform>
|
||
platform ensure --key <key> --display-name <name> [--domain <d>]
|
||
[--url <url>] [--auth-strategy <s>]
|
||
|
||
账号管理:
|
||
account add-web --platform <p> --login-id <id> --login-id-type <type>
|
||
[--label <label>] [--tenant-id <t>] [--environment <env>]
|
||
[--role <role>] [--provider-code <code>] [--url <url>]
|
||
[--extra-json <json>] [--profile-dir <path>]
|
||
[--auth-strategy <s>] [--session-persistent 0|1] [--device-fingerprint <fp>]
|
||
account add-secret --platform <p> --credential-type <type>
|
||
--secret-storage env|windows_credential|none|local_encrypted
|
||
[--secret-ref <env_var>] [--secret-stdin]
|
||
[--label <label>] [--account-id <id>]
|
||
[--environment <env>] [--role <role>]
|
||
account get <id> [--with-credentials]
|
||
account get-credential <account_id> [--reveal --lease-token <token>]
|
||
account list [--platform <p>] [--environment <env>]
|
||
[--tenant-id <t>] [--role <role>] [--limit N]
|
||
account pick-web --platform <p> [--environment <env>] [--tenant-id <t>]
|
||
[--role <role>] [--lease] [--ttl-sec 900]
|
||
[--holder <holder>] [--purpose <purpose>]
|
||
account mark-session <id> --session-status <status>
|
||
account mark-error <id> --code <code> --message <msg>
|
||
account mark-used <id>
|
||
account open <id>
|
||
account init-master-key [--print|--from-env|--rotate]
|
||
account export-credentials --plaintext
|
||
account import-credentials [--replace-all] < backup.json
|
||
|
||
凭据管理:
|
||
credential pick --platform <p> [--type <type>] [--environment <env>]
|
||
[--role <role>] [--reveal]
|
||
credential resolve --id <credential_id> [--reveal]
|
||
|
||
租约管理:
|
||
lease release <lease_token>
|
||
lease list [--active]
|
||
lease cleanup
|
||
|
||
浏览器:
|
||
account open <id>
|
||
open <id> # 兼容别名
|
||
|
||
兼容入口(旧版):
|
||
get <id>
|
||
list-json <platform|all> [--limit N]
|
||
pick-web <platform>
|
||
list [platform|all]
|
||
add <platform> <phone>
|
||
delete id|platform <...>
|
||
"""
|
||
|
||
|
||
# =========================================================================
|
||
# Argument parsing helpers
|
||
# =========================================================================
|
||
|
||
def _get_opt(argv: list[str], flag: str, default=None):
|
||
"""Get value of --flag from argv, returning default if not found."""
|
||
if flag in argv:
|
||
idx = argv.index(flag)
|
||
if idx + 1 < len(argv):
|
||
return argv[idx + 1]
|
||
return default
|
||
|
||
|
||
def _has_flag(argv: list[str], flag: str) -> bool:
|
||
return flag in argv
|
||
|
||
|
||
def _get_int_opt(argv: list[str], flag: str, default: int) -> int:
|
||
v = _get_opt(argv, flag)
|
||
if v is not None:
|
||
try:
|
||
return int(v)
|
||
except ValueError:
|
||
return default
|
||
return default
|
||
|
||
|
||
# =========================================================================
|
||
# Main dispatch
|
||
# =========================================================================
|
||
|
||
def main() -> None:
|
||
setup_skill_logging(SKILL_SLUG, LOG_LOGGER_NAME)
|
||
log = get_skill_logger()
|
||
|
||
if len(sys.argv) < 2:
|
||
print(_USAGE)
|
||
sys.exit(1)
|
||
|
||
log.info("cli_start argv=%s", sys.argv)
|
||
av = sys.argv
|
||
cmd = av[1]
|
||
_maybe_print_paths_debug_cli()
|
||
|
||
# Determine if first arg is a platform key (for legacy shorthand)
|
||
plat_from_first = resolve_platform_key(cmd)
|
||
|
||
# ---- platform subcommand ----
|
||
if cmd == "platform":
|
||
if len(av) < 3:
|
||
print("ERROR:CLI_BAD_ARGS platform 需要子命令:list | get | ensure")
|
||
sys.exit(1)
|
||
sub = av[2]
|
||
if sub == "list":
|
||
domain = _get_opt(av, "--domain")
|
||
cmd_platform_list(domain)
|
||
elif sub == "get":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS platform get 需要 <platform>。")
|
||
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 或 ensure。")
|
||
sys.exit(1)
|
||
|
||
# ---- account subcommand ----
|
||
elif cmd == "account":
|
||
if len(av) < 3:
|
||
print("ERROR:CLI_BAD_ARGS account 需要子命令(add-web / get / …)。")
|
||
print(_USAGE)
|
||
sys.exit(1)
|
||
sub = av[2]
|
||
if sub == "add-web":
|
||
_cmd_add_web(av)
|
||
elif sub == "add-secret":
|
||
_cmd_add_secret(av)
|
||
elif sub == "get-credential":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS account get-credential 需要 <account_id>。")
|
||
sys.exit(1)
|
||
_cmd_get_credential(av)
|
||
elif sub == "get":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS account get 需要 <id>。")
|
||
sys.exit(1)
|
||
aid = av[3]
|
||
with_creds = _has_flag(av, "--with-credentials")
|
||
cmd_account_get(aid, include_credentials=with_creds)
|
||
elif sub == "list":
|
||
_cmd_account_list(av)
|
||
elif sub == "pick-web":
|
||
_cmd_pick_web_new(av)
|
||
elif sub == "mark-session":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS account mark-session 需要 <id>。")
|
||
sys.exit(1)
|
||
aid = av[3]
|
||
ss = _get_opt(av, "--session-status", "unknown")
|
||
cmd_account_mark_session(aid, ss)
|
||
elif sub == "mark-error":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS account mark-error 需要 <id>。")
|
||
sys.exit(1)
|
||
aid = av[3]
|
||
ec = _get_opt(av, "--code", "")
|
||
em = _get_opt(av, "--message", "")
|
||
cmd_account_mark_error(aid, ec, em)
|
||
elif sub == "mark-used":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS account mark-used 需要 <id>。")
|
||
sys.exit(1)
|
||
aid = av[3]
|
||
cmd_account_mark_used(aid)
|
||
elif sub == "open":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS account open 需要 <id>。")
|
||
sys.exit(1)
|
||
cmd_open(av[3])
|
||
elif sub == "init-master-key":
|
||
cmd_account_init_master_key(av)
|
||
elif sub == "export-credentials":
|
||
cmd_account_export_credentials(av)
|
||
elif sub == "import-credentials":
|
||
cmd_account_import_credentials(av)
|
||
else:
|
||
print("ERROR:CLI_BAD_ARGS 未知的 account 子命令。")
|
||
print(_USAGE)
|
||
sys.exit(1)
|
||
|
||
# ---- credential subcommand ----
|
||
elif cmd == "credential":
|
||
if len(av) < 3:
|
||
print("ERROR:CLI_BAD_ARGS credential 需要子命令:pick | resolve")
|
||
sys.exit(1)
|
||
sub = av[2]
|
||
if sub == "pick":
|
||
_cmd_cred_pick(av)
|
||
elif sub == "resolve":
|
||
_cmd_cred_resolve(av)
|
||
else:
|
||
print("ERROR:CLI_BAD_ARGS credential 子命令必须是 pick 或 resolve。")
|
||
sys.exit(1)
|
||
|
||
# ---- lease subcommand ----
|
||
elif cmd == "lease":
|
||
if len(av) < 3:
|
||
print("ERROR:CLI_BAD_ARGS lease 需要子命令:release | list | cleanup")
|
||
sys.exit(1)
|
||
sub = av[2]
|
||
if sub == "release":
|
||
if len(av) < 4:
|
||
print("ERROR:CLI_BAD_ARGS lease release 需要 <lease_token>。")
|
||
sys.exit(1)
|
||
cmd_lease_release(av[3])
|
||
elif sub == "list":
|
||
active = _has_flag(av, "--active")
|
||
cmd_lease_list(active)
|
||
elif sub == "cleanup":
|
||
cmd_lease_cleanup()
|
||
else:
|
||
print("ERROR:CLI_BAD_ARGS lease 子命令必须是 release、list 或 cleanup。")
|
||
sys.exit(1)
|
||
|
||
# ---- Legacy aliases ----
|
||
elif cmd == "get":
|
||
if len(sys.argv) < 3:
|
||
print("ERROR:CLI_GET_MISSING_ID")
|
||
sys.exit(1)
|
||
cmd_get(sys.argv[2])
|
||
|
||
elif cmd == "list-json":
|
||
if len(sys.argv) < 3:
|
||
print("ERROR:CLI_LIST_JSON_MISSING_PLATFORM")
|
||
sys.exit(1)
|
||
av = sys.argv[2:]
|
||
platform_arg = av[0]
|
||
lim = 200
|
||
if "--limit" in av:
|
||
i = av.index("--limit")
|
||
if i + 1 < len(av):
|
||
try:
|
||
lim = int(av[i + 1])
|
||
except ValueError:
|
||
pass
|
||
cmd_list_json(platform_arg, limit=lim)
|
||
|
||
elif cmd == "pick-web":
|
||
if len(sys.argv) < 3:
|
||
print("ERROR:CLI_PICK_WEB_MISSING_ARGS")
|
||
sys.exit(1)
|
||
cmd_pick_web(sys.argv[2])
|
||
|
||
elif cmd == "open":
|
||
if len(sys.argv) < 3:
|
||
print("ERROR:CLI_OPEN_MISSING_ID")
|
||
sys.exit(1)
|
||
cmd_open(sys.argv[2])
|
||
|
||
elif cmd == "list":
|
||
platform_arg = sys.argv[2] if len(sys.argv) >= 3 else "all"
|
||
cmd_list(platform_arg)
|
||
|
||
elif cmd == "add":
|
||
if len(sys.argv) < 4:
|
||
print("ERROR:CLI_ADD_MISSING_ARGS")
|
||
sys.exit(1)
|
||
# Legacy add maps to add-web for backward compat
|
||
cmd_account_add_web(
|
||
platform_input=sys.argv[2],
|
||
login_id=sys.argv[3],
|
||
login_id_type="phone",
|
||
label=None,
|
||
tenant_id=None,
|
||
environment="production",
|
||
role="default",
|
||
provider_code=None,
|
||
url=None,
|
||
extra_json_str=None,
|
||
profile_dir_override=None,
|
||
)
|
||
|
||
elif cmd == "delete":
|
||
if len(sys.argv) < 4:
|
||
print("ERROR:CLI_DELETE_MISSING_ARGS")
|
||
sys.exit(1)
|
||
mode = (sys.argv[2] or "").strip().lower()
|
||
if mode == "id":
|
||
cmd_delete_by_id(sys.argv[3])
|
||
elif mode == "platform":
|
||
if len(sys.argv) >= 5:
|
||
cmd_delete_by_platform_phone(sys.argv[3], sys.argv[4])
|
||
else:
|
||
cmd_delete_by_platform(sys.argv[3])
|
||
else:
|
||
print(f"ERROR:CLI_DELETE_BAD_MODE 不支持的删除方式「{sys.argv[2]}」。")
|
||
sys.exit(1)
|
||
|
||
# ---- Platform shorthand: "python main.py <platform>" ----
|
||
elif plat_from_first:
|
||
cmd_list(sys.argv[1])
|
||
|
||
# ---- Unknown ----
|
||
else:
|
||
print(f"ERROR:CLI_UNKNOWN_OR_BAD_ARGS 子命令「{cmd}」无法识别。")
|
||
print()
|
||
print(_USAGE)
|
||
sys.exit(1)
|
||
|
||
|
||
# =========================================================================
|
||
# Sub-parsers for complex commands
|
||
# =========================================================================
|
||
|
||
def _cmd_add_web(argv: list[str]) -> None:
|
||
plat = _get_opt(argv, "--platform", "")
|
||
login_id = _get_opt(argv, "--login-id")
|
||
login_id_type = _get_opt(argv, "--login-id-type", "unknown")
|
||
label = _get_opt(argv, "--label")
|
||
tenant_id = _get_opt(argv, "--tenant-id")
|
||
environment = _get_opt(argv, "--environment", "production")
|
||
role = _get_opt(argv, "--role", "default")
|
||
provider_code = _get_opt(argv, "--provider-code")
|
||
url = _get_opt(argv, "--url")
|
||
extra_json_str = _get_opt(argv, "--extra-json")
|
||
profile_dir_override = _get_opt(argv, "--profile-dir")
|
||
auth_strategy = _get_opt(argv, "--auth-strategy")
|
||
session_persistent_raw = _get_opt(argv, "--session-persistent")
|
||
device_fingerprint = _get_opt(argv, "--device-fingerprint")
|
||
|
||
session_persistent: Optional[int] = None
|
||
if session_persistent_raw is not None:
|
||
try:
|
||
session_persistent = int(session_persistent_raw)
|
||
except ValueError:
|
||
print("ERROR:CLI_BAD_ARGS --session-persistent 必须是整数(0 或 1)。")
|
||
return
|
||
|
||
if not plat:
|
||
print("ERROR:CLI_BAD_ARGS account add-web 需要 --platform <platform>。")
|
||
return
|
||
cmd_account_add_web(
|
||
platform_input=plat,
|
||
login_id=login_id,
|
||
login_id_type=login_id_type,
|
||
label=label,
|
||
tenant_id=tenant_id,
|
||
environment=environment,
|
||
role=role,
|
||
provider_code=provider_code,
|
||
url=url,
|
||
extra_json_str=extra_json_str,
|
||
profile_dir_override=profile_dir_override,
|
||
auth_strategy=auth_strategy,
|
||
session_persistent=session_persistent,
|
||
device_fingerprint=device_fingerprint,
|
||
)
|
||
|
||
|
||
def _cmd_get_credential(argv: list[str]) -> None:
|
||
aid_raw = argv[3]
|
||
try:
|
||
aid = int(aid_raw)
|
||
except ValueError:
|
||
print(f"ERROR:CLI_BAD_ARGS account_id 必须是整数:{aid_raw}")
|
||
return
|
||
reveal = _has_flag(argv, "--reveal")
|
||
lease_token = _get_opt(argv, "--lease-token")
|
||
cmd_account_get_credential(aid, reveal=reveal, lease_token=lease_token)
|
||
|
||
|
||
def _cmd_add_secret(argv: list[str]) -> None:
|
||
plat = _get_opt(argv, "--platform", "")
|
||
ctype = _get_opt(argv, "--credential-type", "api_key")
|
||
label = _get_opt(argv, "--label")
|
||
storage = _get_opt(argv, "--secret-storage", "env")
|
||
stdin_mode = _has_flag(argv, "--secret-stdin")
|
||
sref = _get_opt(argv, "--secret-ref")
|
||
aid = _get_int_opt(argv, "--account-id", 0) or None
|
||
env = _get_opt(argv, "--environment", "production")
|
||
role = _get_opt(argv, "--role", "api")
|
||
extra_json_str = _get_opt(argv, "--extra-json")
|
||
|
||
if not plat:
|
||
print("ERROR:CLI_BAD_ARGS account add-secret 需要 --platform <platform>。")
|
||
return
|
||
cmd_account_add_secret(
|
||
platform_input=plat,
|
||
credential_type=ctype,
|
||
label=label,
|
||
secret_storage=storage,
|
||
secret_stdin=stdin_mode,
|
||
secret_ref=sref,
|
||
account_id=aid,
|
||
environment=env,
|
||
role=role,
|
||
extra_json_str=extra_json_str,
|
||
)
|
||
|
||
|
||
def _cmd_account_list(argv: list[str]) -> None:
|
||
plat = _get_opt(argv, "--platform")
|
||
env = _get_opt(argv, "--environment")
|
||
tenant = _get_opt(argv, "--tenant-id")
|
||
role = _get_opt(argv, "--role")
|
||
limit = _get_int_opt(argv, "--limit", 200)
|
||
cmd_account_list(
|
||
platform_input=plat,
|
||
environment=env,
|
||
tenant_id=tenant,
|
||
role=role,
|
||
limit=limit,
|
||
)
|
||
|
||
|
||
def _cmd_pick_web_new(argv: list[str]) -> None:
|
||
plat = _get_opt(argv, "--platform", "")
|
||
env = _get_opt(argv, "--environment")
|
||
tenant = _get_opt(argv, "--tenant-id")
|
||
role = _get_opt(argv, "--role")
|
||
do_lease = _has_flag(argv, "--lease")
|
||
ttl = _get_int_opt(argv, "--ttl-sec", 900)
|
||
holder = _get_opt(argv, "--holder")
|
||
purpose = _get_opt(argv, "--purpose")
|
||
|
||
if not plat:
|
||
print("ERROR:CLI_BAD_ARGS account pick-web 需要 --platform <platform>。")
|
||
return
|
||
cmd_account_pick_web(
|
||
platform_input=plat,
|
||
environment=env,
|
||
tenant_id=tenant,
|
||
role=role,
|
||
do_lease=do_lease,
|
||
ttl_sec=ttl,
|
||
holder=holder,
|
||
purpose=purpose,
|
||
)
|
||
|
||
|
||
def _cmd_cred_pick(argv: list[str]) -> None:
|
||
plat = _get_opt(argv, "--platform", "")
|
||
ctype = _get_opt(argv, "--type")
|
||
env = _get_opt(argv, "--environment")
|
||
role = _get_opt(argv, "--role")
|
||
reveal = _has_flag(argv, "--reveal")
|
||
if not plat:
|
||
print("ERROR:CLI_BAD_ARGS credential pick 需要 --platform <platform>。")
|
||
return
|
||
cmd_credential_pick(
|
||
platform_input=plat,
|
||
credential_type=ctype,
|
||
environment=env,
|
||
role=role,
|
||
reveal=reveal,
|
||
)
|
||
|
||
|
||
def _cmd_cred_resolve(argv: list[str]) -> None:
|
||
cid = _get_int_opt(argv, "--id", 0)
|
||
reveal = _has_flag(argv, "--reveal")
|
||
if cid <= 0:
|
||
print("ERROR:CLI_BAD_ARGS credential resolve 需要 --id <credential_id>。")
|
||
return
|
||
cmd_credential_resolve(cid, reveal=reveal)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|