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

+132
View File
@@ -0,0 +1,132 @@
"""Multi-account credential store.
A user hands the CLI a session cookie once (paste it, or pass ``TRIPO_COOKIE``);
from then on the CLI mints short-lived JWTs on demand over plain HTTP — no
browser, no repeated login.
~/.tripo/accounts.json
{
"active": "main",
"accounts": {
"main": {"cookie": "...", "token": "...", "session_expires_at": "...",
"user_id": "...", "email": "...", "added_at": 1791800000}
}
}
"""
import json
import time
from pathlib import Path
DEFAULT_ACCOUNTS_PATH = Path.home() / ".tripo" / "accounts.json"
class Accounts:
def __init__(self, path=None):
self.path = Path(path) if path else DEFAULT_ACCOUNTS_PATH
self.data = {"active": None, "accounts": {}}
self.load()
# --- persistence ---
def load(self):
if self.path.exists():
try:
loaded = json.loads(self.path.read_text(encoding="utf-8"))
self.data = {
"active": loaded.get("active"),
"accounts": loaded.get("accounts", {}),
}
except Exception:
self.data = {"active": None, "accounts": {}}
def save(self):
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps(self.data, indent=2, ensure_ascii=False), encoding="utf-8")
try:
self.path.chmod(0o600) # holds a session cookie: keep it private
except OSError:
pass
# --- queries ---
def names(self):
return sorted(self.data["accounts"])
def rotation(self, start=None):
"""Account names starting at ``start`` (default: active), wrapping around.
Used by the optional auto-fallback so a command can try the next account
when the current one is out of credits.
"""
names = self.names()
if not names:
return []
start = start or self.data.get("active")
if start not in names:
return names
index = names.index(start)
return names[index:] + names[:index]
def exists(self, name):
return name in self.data["accounts"]
def active_name(self):
return self.data.get("active")
def get(self, name=None):
"""Return ``(name, record)`` for ``name`` or the active account."""
name = name or self.data.get("active")
if not name or name not in self.data["accounts"]:
return None, None
return name, self.data["accounts"][name]
# --- mutations ---
def add(self, name, cookie, token=None, session_expires_at=None, user_id=None, email=None):
record = self.data["accounts"].get(name, {})
record.update({
"cookie": cookie,
"token": token,
"session_expires_at": session_expires_at,
"user_id": user_id,
"email": email,
"added_at": record.get("added_at") or int(time.time()),
"updated_at": int(time.time()),
})
self.data["accounts"][name] = record
if not self.data.get("active"):
self.data["active"] = name
self.save()
return name
def use(self, name):
if name not in self.data["accounts"]:
raise KeyError(name)
self.data["active"] = name
self.save()
def remove(self, name):
if name not in self.data["accounts"]:
raise KeyError(name)
del self.data["accounts"][name]
if self.data.get("active") == name:
self.data["active"] = self.names()[0] if self.data["accounts"] else None
self.save()
def update_token(self, name, token, session_expires_at=None):
record = self.data["accounts"].get(name)
if record is None:
return
record["token"] = token
record["updated_at"] = int(time.time())
if session_expires_at:
record["session_expires_at"] = session_expires_at
self.save()
def update_cookie(self, name, cookie):
record = self.data["accounts"].get(name)
if record is None:
return
record["cookie"] = cookie
self.save()