576 lines
22 KiB
Python
576 lines
22 KiB
Python
"""
|
||
llm-manager 主入口 CLI。
|
||
|
||
子命令:
|
||
health 快速离线健康检查
|
||
version 输出版本 JSON
|
||
add <platform> [api_key] 添加 API Key(不传 api_key 则走网页账号关联)
|
||
key list [platform] 列出 Key(打码)
|
||
key del <key_id> 删除 Key
|
||
generate <platform_or_account_id> 生成内容(优先网页模式,备用 API Key 模式)
|
||
"<prompt>"
|
||
|
||
generate 调度规则:
|
||
1. 若 target 为纯数字 → 视为 account-manager 账号 ID → 强制网页模式
|
||
2. 若 target 为平台名/别名:
|
||
a. 先查 account-manager 有无该平台已登录账号 → 网页模式(免费)
|
||
b. 再查 llm_keys 有无可用 Key → API Key 模式(付费)
|
||
c. 两者均无 → 报错并给出操作指引
|
||
"""
|
||
import sys
|
||
import json
|
||
import os
|
||
import asyncio
|
||
import subprocess
|
||
|
||
# Windows GBK 编码兼容修复
|
||
if sys.platform == "win32":
|
||
import io
|
||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
||
|
||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
OPENCLAW_DIR = os.path.dirname(BASE_DIR)
|
||
|
||
# 确保 scripts 目录在 sys.path,使 providers/db 可直接 import
|
||
_scripts_dir = os.path.dirname(os.path.abspath(__file__))
|
||
if _scripts_dir not in sys.path:
|
||
sys.path.insert(0, _scripts_dir)
|
||
|
||
from providers import (
|
||
LLM_PROVIDERS,
|
||
resolve_provider_key,
|
||
provider_list_cn,
|
||
find_logged_in_account,
|
||
resolve_chromium_channel,
|
||
)
|
||
from db import (
|
||
add_key,
|
||
upsert_web_account,
|
||
list_keys,
|
||
list_web_accounts,
|
||
get_key_by_id,
|
||
delete_key,
|
||
find_active_key,
|
||
mark_key_used,
|
||
_mask_key,
|
||
)
|
||
from engines.api_engine import ApiEngine
|
||
from engines.kimi import KimiEngine
|
||
from engines.doubao import DoubaoEngine
|
||
from engines.deepseek import DeepSeekEngine
|
||
from engines.qianwen import QianwenEngine
|
||
from engines.yiyan import YiyanEngine
|
||
from engines.yuanbao import YuanbaoEngine
|
||
|
||
# 平台 slug → 网页引擎类(全部 6 个平台)
|
||
WEB_ENGINES = {
|
||
"doubao": DoubaoEngine,
|
||
"deepseek": DeepSeekEngine,
|
||
"qianwen": QianwenEngine,
|
||
"kimi": KimiEngine,
|
||
"yiyan": YiyanEngine,
|
||
"yuanbao": YuanbaoEngine,
|
||
}
|
||
|
||
SKILL_VERSION = "1.0.3"
|
||
|
||
|
||
def _engine_result_is_error(text: str) -> bool:
|
||
"""网页/API 引擎约定:失败时返回以 ERROR: 开头的字符串,不得当作正文包进 LLM 标记块。"""
|
||
return (text or "").lstrip().startswith("ERROR:")
|
||
|
||
|
||
def _unix_to_iso(ts):
|
||
if ts is None:
|
||
return ""
|
||
try:
|
||
import datetime as _dt
|
||
|
||
return _dt.datetime.fromtimestamp(int(ts)).isoformat(timespec="seconds")
|
||
except Exception:
|
||
return str(ts)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# account-manager 跨 Skill 调用
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _get_account_from_manager(account_id) -> dict | None:
|
||
"""通过 account-manager CLI 按 ID 取账号 JSON。"""
|
||
script = os.path.join(OPENCLAW_DIR, "account-manager", "scripts", "main.py")
|
||
try:
|
||
result = subprocess.run(
|
||
[sys.executable, script, "get", str(account_id)],
|
||
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
||
)
|
||
raw = result.stdout.strip()
|
||
if raw.startswith("ERROR"):
|
||
return None
|
||
return json.loads(raw)
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 网页模式
|
||
# ---------------------------------------------------------------------------
|
||
|
||
async def _run_web_generate(account: dict, prompt: str) -> bool:
|
||
from playwright.async_api import async_playwright
|
||
|
||
platform = account.get("platform", "")
|
||
profile_dir = account.get("profile_dir", "").strip()
|
||
|
||
engine_cls = WEB_ENGINES.get(platform)
|
||
if not engine_cls:
|
||
print(f"ERROR:ENGINE_NOT_FOUND 平台 {platform} 暂无网页引擎实现。")
|
||
return False
|
||
|
||
if not profile_dir or not os.path.isdir(profile_dir):
|
||
print(f"ERROR:PROFILE_DIR_MISSING 账号 {account.get('id')} 的浏览器数据目录不存在:{profile_dir}")
|
||
print("请先通过 account-manager 执行登录:python account-manager/scripts/main.py login <id>")
|
||
return False
|
||
|
||
channel = resolve_chromium_channel()
|
||
if not channel:
|
||
print("ERROR:浏览器未找到。请安装 Google Chrome 或 Microsoft Edge 后重试。")
|
||
return False
|
||
|
||
print(f"[llm-manager] 使用网页模式 | 平台: {LLM_PROVIDERS.get(platform, {}).get('label', platform)} | 账号: {account.get('name', account.get('id'))}")
|
||
|
||
async with async_playwright() as p:
|
||
browser = await p.chromium.launch_persistent_context(
|
||
user_data_dir=profile_dir,
|
||
headless=False,
|
||
channel=channel,
|
||
no_viewport=True,
|
||
permissions=["clipboard-read", "clipboard-write"],
|
||
args=["--start-maximized"],
|
||
)
|
||
try:
|
||
page = browser.pages[0] if browser.pages else await browser.new_page()
|
||
engine = engine_cls(page)
|
||
try:
|
||
result = await engine.generate(prompt)
|
||
except Exception as e:
|
||
print(f"ERROR:WEB_GENERATE_FAILED 网页模式生成失败:{e}")
|
||
return False
|
||
|
||
result = (result or "").strip()
|
||
if _engine_result_is_error(result):
|
||
print(result)
|
||
return False
|
||
|
||
print("===LLM_START===")
|
||
print(result)
|
||
print("===LLM_END===")
|
||
return True
|
||
finally:
|
||
await asyncio.sleep(3)
|
||
await browser.close()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# API Key 模式
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _run_api_generate(provider_key: str, key_record: dict, prompt: str) -> bool:
|
||
provider = LLM_PROVIDERS[provider_key]
|
||
api_base = provider.get("api_base")
|
||
model = (key_record.get("default_model") or "").strip() or (provider.get("api_model") or "").strip()
|
||
|
||
if not api_base:
|
||
print(f"ERROR:NO_API_BASE {provider['label']} 暂无 API 地址(不支持 API 模式)。")
|
||
return False
|
||
if not model:
|
||
print(
|
||
f"ERROR:API_MODEL_MISSING {provider['label']} 需要指定模型名称。\n"
|
||
f"提示:{provider.get('api_note', '')}\n"
|
||
f"请删除该 Key 并重新添加时带上 --model 参数:python main.py add {provider_key} \"Key\" --model \"模型名\""
|
||
)
|
||
return False
|
||
|
||
print(f"[llm-manager] 使用 API Key 模式 | 平台: {provider['label']} | 模型: {model}")
|
||
|
||
try:
|
||
engine = ApiEngine(api_base=api_base, api_key=key_record["api_key"], model=model)
|
||
result = engine.generate(prompt)
|
||
except Exception as e:
|
||
print(f"ERROR:API_GENERATE_FAILED API 模式调用失败:{e}")
|
||
return False
|
||
result = (result or "").strip()
|
||
if _engine_result_is_error(result):
|
||
print(result)
|
||
return False
|
||
mark_key_used(key_record["id"])
|
||
|
||
print("===LLM_START===")
|
||
print(result)
|
||
print("===LLM_END===")
|
||
return True
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# generate 命令
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def cmd_generate(target: str, prompt: str) -> bool:
|
||
"""
|
||
target 可以是:
|
||
- 纯数字 → account-manager 账号 ID(强制网页模式)
|
||
- 平台名/别名 → 自动选择模式(网页优先,API Key 备用)
|
||
成功返回 True,任一步失败返回 False(调用方应设进程退出码非 0)。
|
||
"""
|
||
target = (target or "").strip()
|
||
prompt = (prompt or "").strip()
|
||
if not prompt:
|
||
print("ERROR:PROMPT_EMPTY 提示词不能为空。")
|
||
return False
|
||
|
||
# ---- 情况 1:显式 account_id(纯数字)→ 强制网页模式 ----
|
||
if target.isdigit():
|
||
account = _get_account_from_manager(target)
|
||
if not account:
|
||
print(f"ERROR:ACCOUNT_NOT_FOUND 未在「模型管理」对应的账号列表中找到 id={target}。")
|
||
print("请先在模型管理中添加该平台账号,或检查 id 是否抄错。")
|
||
return False
|
||
if account.get("login_status") != 1:
|
||
print(
|
||
f"ERROR:REQUIRE_LOGIN 账号「{account.get('name', target)}」尚未完成网页登录。\n"
|
||
"请先在「模型管理」里确认已添加该账号,再执行下面命令完成登录:\n"
|
||
f" python account-manager/scripts/main.py login {target}\n"
|
||
"若使用网页模式:请先在对应平台官网注册好账号,再回到本工具添加并登录。"
|
||
)
|
||
return False
|
||
return asyncio.run(_run_web_generate(account, prompt))
|
||
|
||
# ---- 情况 2:平台名 → 自动选模式 ----
|
||
provider_key = resolve_provider_key(target)
|
||
if not provider_key:
|
||
print(f"ERROR:INVALID_PLATFORM 无法识别的平台「{target}」。")
|
||
print("支持的平台:" + provider_list_cn())
|
||
return False
|
||
|
||
provider = LLM_PROVIDERS[provider_key]
|
||
label = provider["label"]
|
||
web_url = (provider.get("web_url") or "").strip()
|
||
|
||
# 优先级 1:查 account-manager 已登录账号(网页模式,免费)
|
||
account = find_logged_in_account(provider_key)
|
||
if account:
|
||
return asyncio.run(_run_web_generate(account, prompt))
|
||
|
||
# 优先级 2:查本地 API Key(付费)
|
||
key_record = find_active_key(provider_key)
|
||
if key_record:
|
||
return _run_api_generate(provider_key, key_record, prompt)
|
||
|
||
# 两种凭据均无 → 明确说明「模型管理 + 网页模式需官网账号」
|
||
print(f"ERROR:NO_CREDENTIAL 当前没有可用的「{label}」调用凭据(既没有已登录的网页账号,也没有可用的 API Key)。")
|
||
print()
|
||
print("【推荐 · 网页模式(免费)】按顺序做:")
|
||
print(f" ① 打开「模型管理」(与 OpenClaw 里 account-manager 账号管理是同一套数据),先把「{label}」账号添加进去。")
|
||
print(" 命令行示例(在 OpenClaw 根目录执行):")
|
||
print(f" python account-manager/scripts/main.py add \"{label}\" \"你的手机号或登录名\"")
|
||
print(" python account-manager/scripts/main.py login <上一步返回的账号 id>")
|
||
print(" ② 若走网页模式:请先在对应平台官方网站注册并创建好账号(否则浏览器里登录会失败)。")
|
||
if web_url:
|
||
print(f" 「{label}」网页入口:{web_url}")
|
||
print()
|
||
if provider.get("has_api"):
|
||
print("【备选 · API Key(付费)】若已在厂商控制台开通 API,可本地登记 Key 后走接口调用:")
|
||
print(f" python llm-manager/scripts/main.py add {provider_key} \"你的API Key\"")
|
||
if provider.get("api_note"):
|
||
print(f" 说明:{provider['api_note']}")
|
||
else:
|
||
print(f"说明:「{label}」暂无公开 API,只能使用上面的网页模式。")
|
||
return False
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# key 子命令
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def cmd_key_add(provider_input: str, api_key: str = "", model: str = None, label: str = ""):
|
||
provider_key = resolve_provider_key(provider_input)
|
||
if not provider_key:
|
||
print(f"ERROR:INVALID_PLATFORM 无法识别的平台「{provider_input}」。")
|
||
print("支持:" + provider_list_cn())
|
||
return
|
||
provider = LLM_PROVIDERS[provider_key]
|
||
api_key = (api_key or "").strip()
|
||
|
||
# 不传 api_key:默认走网页版本,检查 account-manager 是否已有该平台已登录账号
|
||
if not api_key:
|
||
account = find_logged_in_account(provider_key)
|
||
if account:
|
||
web_row_id = upsert_web_account(
|
||
provider=provider_key,
|
||
account_id=int(account.get("id")),
|
||
account_name=(account.get("name") or account.get("account_name") or ""),
|
||
login_status=int(account.get("login_status") or 0),
|
||
)
|
||
print(
|
||
f"OK:WEB_ACCOUNT_READY 已关联网页模式账号 | 平台: {provider['label']} "
|
||
f"| account_id: {account.get('id')} | 账号: {account.get('name') or account.get('account_name') or ''}"
|
||
)
|
||
print(f"已写入 llm-manager 记录:WEB_ID {web_row_id}")
|
||
print(f"后续可直接调用:python main.py generate {provider_key} \"<提示词>\"")
|
||
return
|
||
print(f"ERROR:WEB_ACCOUNT_NOT_FOUND 未找到已登录的「{provider['label']}」账号,暂时无法走网页模式。")
|
||
print("请先在 account-manager 添加并登录该平台账号:")
|
||
print(f" python account-manager/scripts/main.py add \"{provider['label']}\" \"你的登录名\"")
|
||
print(" python account-manager/scripts/main.py login <账号id>")
|
||
if provider.get("has_api"):
|
||
print("或直接提供 API Key:")
|
||
print(f" python main.py add {provider_key} \"<API_Key>\"")
|
||
return
|
||
|
||
if not provider.get("has_api"):
|
||
print(f"ERROR:{provider['label']} 暂无公开 API,不支持 API Key 模式。")
|
||
print("请改用网页模式并先在 account-manager 完成登录。")
|
||
return
|
||
|
||
new_id = add_key(provider=provider_key, api_key=api_key, model=model, label=label)
|
||
print(f"✅ 已保存 API Key:ID {new_id} | {provider['label']} | 模型: {model or provider.get('api_model') or '(未指定)'} | {_mask_key(api_key)}")
|
||
if not model and not provider.get("api_model"):
|
||
print(f"⚠️ 注意:该平台需要指定模型名称,否则调用时会报错。")
|
||
print(f" {provider.get('api_note', '')}")
|
||
print(f" 可删除后重新添加:python main.py key del {new_id}")
|
||
|
||
|
||
def cmd_key_list(provider_input: str = None, limit: int = 10):
|
||
provider_key = None
|
||
if provider_input:
|
||
provider_key = resolve_provider_key(provider_input)
|
||
if not provider_key:
|
||
print(f"ERROR:INVALID_PLATFORM 无法识别的平台「{provider_input}」。")
|
||
return
|
||
|
||
keys = list_keys(provider_key, limit=limit)
|
||
web_accounts = list_web_accounts(provider_key, limit=limit)
|
||
if not keys and not web_accounts:
|
||
print("暂无记录(API Key / 网页账号关联 都为空)。")
|
||
print("添加 API Key:python main.py add <platform> \"API Key\" [--model 模型名]")
|
||
print("添加网页账号关联:python main.py add <platform>")
|
||
return
|
||
|
||
sep_line = "_" * 39
|
||
rows = []
|
||
for k in keys:
|
||
rows.append({
|
||
"type": "api_key",
|
||
"created_at": int(k.get("created_at") or 0),
|
||
"payload": k,
|
||
})
|
||
for w in web_accounts:
|
||
rows.append({
|
||
"type": "web_account",
|
||
"created_at": int(w.get("created_at") or 0),
|
||
"payload": w,
|
||
})
|
||
rows.sort(key=lambda x: (x["created_at"], int(x["payload"].get("id") or 0)), reverse=True)
|
||
|
||
for idx, row in enumerate(rows):
|
||
if row["type"] == "api_key":
|
||
k = row["payload"]
|
||
print("record_type:api_key")
|
||
print(f"id:{k['id']}")
|
||
print(f"platform:{k['provider']}")
|
||
print(f"platform_cn:{LLM_PROVIDERS.get(k['provider'], {}).get('label', k['provider'])}")
|
||
print(f"label:{k.get('label') or ''}")
|
||
print(f"api_key:{_mask_key(k.get('api_key') or '')}")
|
||
print(f"default_model:{k.get('default_model') or ''}")
|
||
print(f"is_active:{int(k.get('is_active') or 0)}")
|
||
print(f"last_used_at:{_unix_to_iso(k.get('last_used_at'))}")
|
||
print(f"created_at:{_unix_to_iso(k.get('created_at'))}")
|
||
else:
|
||
w = row["payload"]
|
||
print("record_type:web_account")
|
||
print(f"id:{w['id']}")
|
||
print(f"platform:{w['provider']}")
|
||
print(f"platform_cn:{LLM_PROVIDERS.get(w['provider'], {}).get('label', w['provider'])}")
|
||
print(f"account_id:{w.get('account_id')}")
|
||
print(f"account_name:{w.get('account_name') or ''}")
|
||
print(f"login_status:{int(w.get('login_status') or 0)}")
|
||
print(f"created_at:{_unix_to_iso(w.get('created_at'))}")
|
||
print(f"updated_at:{_unix_to_iso(w.get('updated_at'))}")
|
||
if idx != len(rows) - 1:
|
||
print(sep_line)
|
||
print()
|
||
|
||
|
||
def cmd_key_del(key_id_str: str):
|
||
if not key_id_str.isdigit():
|
||
print(f"ERROR:INVALID_ID key_id 须为正整数,收到「{key_id_str}」。")
|
||
return
|
||
key_id = int(key_id_str)
|
||
key = get_key_by_id(key_id)
|
||
if not key:
|
||
print(f"ERROR:KEY_NOT_FOUND 未找到 ID={key_id} 的 API Key。")
|
||
return
|
||
delete_key(key_id)
|
||
label_str = f"({key['label']})" if key.get("label") else ""
|
||
print(f"✅ 已删除:ID {key_id} | {LLM_PROVIDERS.get(key['provider'], {}).get('label', key['provider'])}{label_str} | {_mask_key(key['api_key'])}")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# health / version
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def cmd_health():
|
||
ok = sys.version_info >= (3, 10)
|
||
sys.exit(0 if ok else 1)
|
||
|
||
|
||
def cmd_version():
|
||
print(json.dumps({"version": SKILL_VERSION, "skill": "llm-manager"}, ensure_ascii=False))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# CLI 解析
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _print_usage():
|
||
print("用法:")
|
||
print(" python main.py health")
|
||
print(" python main.py version")
|
||
print(" python main.py generate <平台名或account_id> \"<提示词>\"")
|
||
print(" python main.py add <平台> [API_Key] [--model 模型名] [--label 备注]")
|
||
print(" python main.py list [平台] [--limit 条数]")
|
||
print(" python main.py del <key_id>")
|
||
print()
|
||
print("支持的平台:" + provider_list_cn())
|
||
print()
|
||
print("generate 说明:")
|
||
print(" · 传入 account_id(数字)→ 指定账号网页模式(需先用 account-manager 登录)")
|
||
print(" · 传入平台名 → 自动选择:优先网页模式(免费),无可用账号时才用 API Key(付费)")
|
||
print()
|
||
print("兼容说明:key add/list/del 旧写法仍可用。")
|
||
|
||
|
||
def main(argv=None) -> int:
|
||
args = argv if argv is not None else sys.argv[1:]
|
||
if not args:
|
||
_print_usage()
|
||
return 1
|
||
if args[0] in ("-h", "--help"):
|
||
_print_usage()
|
||
return 0
|
||
|
||
def _parse_list_args(rest_args):
|
||
platform_filter = None
|
||
limit = 10
|
||
i = 0
|
||
while i < len(rest_args):
|
||
if rest_args[i] == "--limit" and i + 1 < len(rest_args):
|
||
try:
|
||
limit = int(rest_args[i + 1])
|
||
except ValueError:
|
||
print(f"ERROR:CLI_KEY_LIST_BAD_LIMIT limit 必须是整数,收到「{rest_args[i + 1]}」。")
|
||
return None, None, 1
|
||
i += 2
|
||
else:
|
||
if platform_filter is None:
|
||
platform_filter = rest_args[i]
|
||
i += 1
|
||
return platform_filter, limit, 0
|
||
|
||
def _parse_add_args(rest_args):
|
||
if len(rest_args) < 1:
|
||
print("ERROR:CLI_ADD_MISSING_ARGS")
|
||
print("用法:python main.py add <平台> [API_Key] [--model 模型] [--label 备注]")
|
||
return None, None, None, None, 1
|
||
platform_arg = rest_args[0]
|
||
api_key_arg = ""
|
||
model_arg = None
|
||
label_arg = ""
|
||
i = 1
|
||
if i < len(rest_args) and not rest_args[i].startswith("--"):
|
||
api_key_arg = rest_args[i]
|
||
i += 1
|
||
while i < len(rest_args):
|
||
if rest_args[i] == "--model" and i + 1 < len(rest_args):
|
||
model_arg = rest_args[i + 1]
|
||
i += 2
|
||
elif rest_args[i] == "--label" and i + 1 < len(rest_args):
|
||
label_arg = rest_args[i + 1]
|
||
i += 2
|
||
else:
|
||
i += 1
|
||
return platform_arg, api_key_arg, model_arg, label_arg, 0
|
||
|
||
cmd = args[0]
|
||
|
||
if cmd == "health":
|
||
cmd_health()
|
||
|
||
elif cmd == "version":
|
||
cmd_version()
|
||
|
||
elif cmd == "generate":
|
||
if len(args) < 3:
|
||
print("ERROR:CLI_GENERATE_MISSING_ARGS")
|
||
print("用法:python main.py generate <平台名或account_id> \"<提示词>\"")
|
||
return 1
|
||
if not cmd_generate(args[1], args[2]):
|
||
return 1
|
||
|
||
elif cmd == "list":
|
||
platform_filter, limit, err = _parse_list_args(args[1:])
|
||
if err:
|
||
return err
|
||
cmd_key_list(platform_filter, limit=limit)
|
||
|
||
elif cmd == "add":
|
||
platform_arg, api_key_arg, model_arg, label_arg, err = _parse_add_args(args[1:])
|
||
if err:
|
||
return err
|
||
cmd_key_add(platform_arg, api_key_arg, model=model_arg, label=label_arg)
|
||
|
||
elif cmd == "del":
|
||
if len(args) < 2:
|
||
print("ERROR:CLI_DEL_MISSING_ARGS 用法:python main.py del <key_id>")
|
||
return 1
|
||
cmd_key_del(args[1])
|
||
|
||
elif cmd == "key":
|
||
if len(args) < 2:
|
||
print("ERROR:CLI_KEY_MISSING_SUBCOMMAND 请指定子命令:add / list / del")
|
||
return 1
|
||
sub = args[1]
|
||
|
||
if sub == "add":
|
||
platform_arg, api_key_arg, model_arg, label_arg, err = _parse_add_args(args[2:])
|
||
if err:
|
||
return err
|
||
cmd_key_add(platform_arg, api_key_arg, model=model_arg, label=label_arg)
|
||
|
||
elif sub == "list":
|
||
platform_filter, limit, err = _parse_list_args(args[2:])
|
||
if err:
|
||
return err
|
||
cmd_key_list(platform_filter, limit=limit)
|
||
|
||
elif sub == "del":
|
||
if len(args) < 3:
|
||
print("ERROR:CLI_KEY_DEL_MISSING_ARGS 用法:python main.py key del <key_id>")
|
||
return 1
|
||
cmd_key_del(args[2])
|
||
|
||
else:
|
||
print(f"ERROR:CLI_UNKNOWN_KEY_SUB 未知 key 子命令「{sub}」,支持:add / list / del")
|
||
return 1
|
||
|
||
else:
|
||
print(f"ERROR:CLI_UNKNOWN_COMMAND 未知命令「{cmd}」。")
|
||
_print_usage()
|
||
return 1
|
||
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|