tripocli v2.0.0:Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯 Python 标准库、零第三方依赖)

覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、
纹理、PBR、智能 UV、部件补全、格式导出与下载。

代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
This commit is contained in:
Mrpzx committed 2026-10-13 19:22:54 +08:00
commit 977f97995a
60 files changed
+5622

No files matched your search

+189
View File
@@ -0,0 +1,189 @@
"""账号管理 — add / list / use / remove / show.
给别人用时不需要浏览器:把浏览器的 cookie 粘贴进来一次,CLI 之后自己续期 JWT。
tripocli account add --name work --cookie "ory_kratos_session=...; csrf_token_...=..."
tripocli account add # 交互式粘贴
tripocli account list
tripocli account use work
tripocli account remove work
"""
import getpass
import sys
import time
from .. import auth
from ..accounts import Accounts
from ..config import Config
COOKIE_HINT = (
"请粘贴 cookie(整条 Cookie 字符串,或仅 ory_kratos_session 的值),回车结束:\n"
" 提示:浏览器 DevTools → Network → 任意 api.tripo3d.ai 请求 → Request Headers → Cookie 整条复制"
)
def _fmt(epoch):
if not epoch:
return None
if isinstance(epoch, (int, float)):
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch))
return str(epoch)
def _mask(value, keep=10):
if not value:
return None
return f"{value[:keep]}...{value[-6:]}" if len(value) > keep + 8 else "***"
def _read_cookie(args):
if args.cookie_file:
with open(args.cookie_file, "r", encoding="utf-8") as handle:
return handle.read().strip()
if args.cookie == "-":
return sys.stdin.read().strip()
if args.cookie:
return args.cookie.strip()
# Interactive paste; hidden input so the cookie does not land in scrollback.
print(COOKIE_HINT)
try:
return getpass.getpass("cookie: ").strip()
except (EOFError, KeyboardInterrupt):
raise SystemExit("已取消")
def _identity(api_base, cookie, device_id):
"""Best-effort identity lookup so accounts are recognisable in `account list`."""
try:
return auth.fetch_identity(api_base, cookie, device_id)
except Exception:
return None, None
def _add(ctx):
args = ctx.args
cookie = auth.normalize_cookie(_read_cookie(args))
# Validate before storing: a bad cookie must fail here, not on the next command.
token, session_expires = auth.mint_token(ctx.config.api_base, cookie, ctx.config.device_id)
user_id, email = _identity(ctx.config.api_base, cookie, ctx.config.device_id)
name = args.name or email or user_id or "default"
ctx.accounts.add(name, cookie, token=token, session_expires_at=session_expires,
user_id=user_id, email=email)
payload = {
"added": name,
"active": ctx.accounts.active_name(),
"email": email,
"user_id": user_id,
"session_expires_at": _fmt(session_expires),
}
ctx.out.data(
payload,
human=lambda: (
ctx.out.line(f"✅ 账号已保存: {name}"),
ctx.out.line(f"身份 : {email or user_id or '未知'}"),
ctx.out.line(f"会话有效期: {_fmt(session_expires) or '未知'}"),
ctx.out.line("之后无需浏览器,CLI 会自动用 cookie 续期 JWT"),
),
)
return payload
def _list(ctx):
accounts = ctx.accounts
active = accounts.active_name()
items = []
for name in accounts.names():
record = accounts.data["accounts"][name]
items.append({
"name": name,
"active": name == active,
"email": record.get("email"),
"user_id": record.get("user_id"),
"session_expires_at": _fmt(record.get("session_expires_at")),
"cookie": _mask(record.get("cookie")),
})
def human():
if not items:
ctx.out.line("暂无账号。用 `tripocli account add` 粘贴 cookie 添加。")
return
for item in items:
mark = "*" if item["active"] else " "
ctx.out.line(f"{mark} {item['name']:<20} {item['email'] or item['user_id'] or '-'}"
f" (会话至 {item['session_expires_at'] or '未知'})")
ctx.out.data({"active": active, "accounts": items}, human=human)
return items
def _use(ctx):
name = ctx.args.name
try:
ctx.accounts.use(name)
except KeyError:
raise ValueError(f"账号不存在: {name}(可用 `tripocli account list` 查看)")
ctx.out.data({"active": name}, human=lambda: ctx.out.line(f"✅ 当前账号已切换为 {name}"))
return {"active": name}
def _remove(ctx):
name = ctx.args.name
try:
ctx.accounts.remove(name)
except KeyError:
raise ValueError(f"账号不存在: {name}")
payload = {"removed": name, "active": ctx.accounts.active_name()}
ctx.out.data(payload, human=lambda: ctx.out.line(f"✅ 已删除账号 {name}"))
return payload
def _show(ctx):
name = ctx.args.name
resolved, record = ctx.accounts.get(name)
if not record:
raise ValueError(f"账号不存在: {name or '(无当前账号)'}")
info = {
"name": resolved,
"active": resolved == ctx.accounts.active_name(),
"email": record.get("email"),
"user_id": record.get("user_id"),
"cookie": _mask(record.get("cookie")),
"session_expires_at": _fmt(record.get("session_expires_at")),
"added_at": _fmt(record.get("added_at")),
}
ctx.out.data(info, human=lambda: (
ctx.out.line(f"账号 : {resolved}{' (当前)' if info['active'] else ''}"),
ctx.out.line(f"身份 : {record.get('email') or record.get('user_id') or '未知'}"),
ctx.out.line(f"Cookie : {info['cookie']}"),
ctx.out.line(f"会话有效期: {info['session_expires_at'] or '未知'}"),
))
return info
def register(subparsers):
parser = subparsers.add_parser("account", help="账号管理(粘贴 cookie,多账号切换)")
sub = parser.add_subparsers(dest="account_action", required=True)
add = sub.add_parser("add", help="添加账号(粘贴 cookie,无需浏览器)")
add.add_argument("--name", help="账号名(默认取邮箱/用户 id)")
add.add_argument("--cookie", help="cookie 值,或整条 Cookie 串;传 - 从 stdin 读取")
add.add_argument("--cookie-file", help="从文件读取 cookie")
add.set_defaults(func=_add)
sub.add_parser("list", help="列出所有账号").set_defaults(func=_list)
use = sub.add_parser("use", help="切换当前账号")
use.add_argument("name")
use.set_defaults(func=_use)
remove = sub.add_parser("remove", help="删除账号")
remove.add_argument("name")
remove.set_defaults(func=_remove)
show = sub.add_parser("show", help="查看账号详情")
show.add_argument("name", nargs="?")
show.set_defaults(func=_show)