tripo-cli 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +16 -14
- package/dist/ai/experts.js +26 -20
- package/dist/ai/plan-card.js +73 -34
- package/dist/ai/wizard.js +19 -19
- package/dist/commands/account.js +2 -6
- package/dist/commands/ai.js +18 -16
- package/dist/commands/batch.js +58 -18
- package/dist/commands/completion.js +1 -1
- package/dist/commands/config-cmd.js +1 -1
- package/dist/commands/doctor.js +1 -3
- package/dist/commands/generate.js +50 -8
- package/dist/commands/home.js +12 -13
- package/dist/commands/login.js +125 -182
- package/dist/commands/make.js +50 -25
- package/dist/commands/mcp.js +8 -3
- package/dist/commands/process-groups.js +29 -18
- package/dist/commands/shared.js +22 -13
- package/dist/commands/task.js +12 -11
- package/dist/commands/topup.js +6 -10
- package/dist/commands/update.js +23 -11
- package/dist/commands/view.js +3 -4
- package/dist/config/config.js +1 -1
- package/dist/core/analytics.js +39 -16
- package/dist/core/client.js +53 -21
- package/dist/core/device-auth.js +49 -1
- package/dist/core/download.js +2 -1
- package/dist/core/errors.js +24 -14
- package/dist/core/output.js +0 -4
- package/dist/core/requests.js +94 -31
- package/dist/core/task-service.js +21 -20
- package/dist/core/upload.js +23 -5
- package/dist/core/watch.js +3 -0
- package/dist/knowledge/chains.js +16 -17
- package/dist/knowledge/error-catalog.js +39 -37
- package/dist/knowledge/models.js +147 -27
- package/dist/knowledge/params.js +143 -32
- package/dist/knowledge/scenarios.js +32 -55
- package/dist/pipeline/make.js +38 -30
- package/dist/pipeline/runner.js +27 -8
- package/dist/pipeline/stdin.js +40 -3
- package/dist/program.js +3 -10
- package/dist/util/input-detect.js +10 -1
- package/package.json +3 -2
- package/skill/SKILL.md +52 -15
- package/skill/commands/account.md +8 -3
- package/skill/commands/batch.md +13 -3
- package/skill/commands/generate.md +57 -23
- package/skill/commands/make.md +43 -16
- package/skill/commands/process.md +29 -24
- package/skill/commands/task.md +4 -4
- package/skill/commands/view.md +1 -1
- package/skill/common-errors.md +14 -10
- package/skill/examples/animation.md +4 -2
- package/skill/examples/ar-web.md +6 -4
- package/skill/examples/game-asset.md +18 -7
- package/skill/examples/pipes.md +2 -2
- package/skill/examples/print.md +7 -3
- package/dist/i18n.js +0 -19
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
- Preflight every deterministic `make` chain request before uploads or generation. Normal runs and dry runs use the same validation; direct `executeMake` validates before balance/generation calls. Runtime source-dependent checks remain in place.
|
|
6
|
+
- Normalize `H3.1`, `P1.0`, `P2.0` (also `P2.0 Preview`) and legacy `H3.0` / `H2.5` display aliases case-insensitively, alongside existing `tripo-*` names. Reject unknown 3D models; require finite integer generation/convert/decimate face limits; enforce rig version/body/spec and texture-quality compatibility.
|
|
7
|
+
- Validate text-to-image and image-to-image model whitelists, quality, background/format linkage and documented size constraints. Reject `seedream_v4` on image editing and retired `chat_image_1` on both endpoints. Keep `chat_image_1.5` active (announced retirement: 2026-12-01). Never silently switch models or resubmit.
|
|
8
|
+
- Image editing validates deterministic parameters before local uploads. Unknown extra parameter keys remain passthrough; they are not claimed as validated.
|
|
9
|
+
- Derive plan-card details and deliverables from normalized requests, include adjustment warnings, and retain scenario tips only when their assumptions still hold. Explicit model/parameter overrides no longer inherit misleading P2, quad-output or HD-cost claims. P2 is no longer labelled Preview in plan cards, scenario notes or bundled docs. Remove fixed-credit quotes from retopology model-selection warnings.
|
|
10
|
+
- Estimate the generation step locally from official list prices (identical on ov and cn): H3.1 10–40 credits for text and 20–50 for image/multiview by texture tier, plus `geometry_quality=detailed` +20, `quad` +5, `smart_low_poly` +10, `generate_parts` +20; P2 100–130 for every input. Plan cards show it (× `-n` candidates) and dry-run JSON adds `generation_credits_estimate`. P1, legacy models, imports and chain steps stay "not estimated" (`null`), never zero. P2 quad notes no longer claim a higher price tier.
|
|
11
|
+
- `texture=false` without an explicit `pbr` now also sends `pbr=false` (with a warning): `pbr` defaults to true server-side and forces texturing, so the bare-geometry request and its estimate stay consistent.
|
|
12
|
+
- Treat H3.1 as the official default model (`official default → v3.1`). Face budgets below P2's minimum (48) now select H3.1, like budgets above P2's maximum. Narrow real-time prompt hints: board/card/tabletop games, gaming PCs, consoles and animation styles (`animation-style`, `动画风格`, `动画片`) no longer select P2.
|
|
13
|
+
- Complete static preflight on standalone image/multiview-to-model and headless multi-candidate chains before local uploads. Reject deterministic rig/preset-family conflicts before generation, including direct executeMake/runChain callers; do not guess a standalone retarget source's rig version.
|
|
14
|
+
- Validate the effective 3D text prompt's 1024-character limit and image-editing reference limits (Seedream 4, Banana 10, Chat Image 16) before submission/upload.
|
|
15
|
+
- Do not inject a biped-only spec into the built-in anim scenario; a quadruped rig-check recommendation can proceed. Explicit unsupported specs are still rejected locally rather than silently dropped.
|
|
16
|
+
- Handle explicit Chinese/English dense-geometry negation per mention, while retaining separate positive requirements. This is a lexical safeguard, not full language understanding; use an explicit model for ambiguous intent.
|
|
17
|
+
- Preserve existing P-series unsupported-field removal with warnings; normalized payloads, not the original unsupported combination, are what local validation approves.
|
|
18
|
+
- Bundled skill: H3.1 is described as the official default; presets outrank prompt hints and list their models; agents relay the P2 estimate and get confirmation before running it, and ask before `tripo redo` (a new paid task with no plan card or confirmation). P1 is no longer described as fast. The `batch` docs add a Cost section: every job is a full `make` with no plan card or confirmation, so agents estimate the run from a dry run per preset and get confirmation first.
|
|
19
|
+
- All CLI output (logs, plan cards, error messages and suggestions, interactive prompts, help text) is English only. The `language` config key and `LANG`/`LC_ALL`-based switching are removed; Chinese prompts are still understood.
|
|
20
|
+
- `batch run` never resubmits a job once a paid task may exist. It retries only when the failed attempt left no task behind (it failed before submission, or its only task failed and was refunded). Otherwise the job stops as `needs_attention` with `tasks`, `error` and `suggestion` in the state file, is skipped on re-run, and counts toward exit 6 (the summary adds `needs_attention`).
|
|
21
|
+
- Strict pipes: when stdin is not a terminal, process commands without an explicit input never fall back to `@last`. Empty stdin or no `task_id` exits 2; an upstream error JSON exits with the upstream's code; nothing is submitted in either case.
|
|
22
|
+
- A task that succeeds but whose artifact download fails now exits 7 instead of reporting success; the error JSON carries `task_id` and suggests `tripo task get <id> --download`. `make -n` keeps such candidates with `download_error` (exit 7). Error JSON for failed or timed-out tasks also carries `task_id`.
|
|
23
|
+
- `task watch --json` writes progress to stderr and stdout carries only the final JSON line; the NDJSON progress events on stdout are removed. Docs now state the contract as "every result command prints exactly one JSON line" (`docs`/`completion` print text, `mcp` speaks JSON-RPC).
|
|
24
|
+
- The bundled `SKILL.md` starts with YAML frontmatter (`name: tripo-cli`, `description`) so skill loaders can discover it.
|
|
25
|
+
- Fix the color 3D-print recipe: `--for print -p texture=true --then convert:format=OBJ,export_vertex_colors=true`. API 3MF output is single color, so build a multi-color 3MF in the slicer; the print preset no longer lists "3MF (colored)" as a deliverable.
|
|
26
|
+
- `--for ar-web` generates P2 with `face_limit=20000` instead of a follow-up decimate step (saves the 30-credit decimate) and downloads the generated GLB as well as the USDZ. The result adds `source_output_dir` / `source_model_file`, and the plan card lists both deliverables. `--then` replaces the preset chain and keeps the final-step-only download.
|
|
27
|
+
- Update bundled skill and capability documentation and add unit/mock CLI preflight regressions. No real generation, billing or credentials are required by these tests.
|
|
28
|
+
|
|
29
|
+
Known precision boundary: official Seedream size documentation gives rounded MP ranges. This release validates syntax/tiers/aspect ratio, not an invented exact pixel-count cutoff; Banana Pro undocumented pixel limits and arbitrary passthrough fields remain server-validated. Mesh edit stays experimental and disabled by default.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# tripo-cli
|
|
2
2
|
|
|
3
|
+
0.8.0 validates all statically known `make` chain requests before uploads or generation, including normal runs, dry runs and direct `executeMake` calls. `H3.1` / `P1.0` / `P2.0` (plus legacy `H3.0` / `H2.5`, case-insensitive) normalize to dated wire IDs and share the same limits as `tripo-*` aliases; unknown 3D/image models are rejected. Image generation validates endpoint-specific models, quality, background, output format and documented size rules before image-edit uploads. `chat_image_1` is retired and rejected; `chat_image_1.5` remains supported until its announced 2026-12-01 retirement. Plans show scenario notes and the official list-price estimate for the generation step; P1, legacy models and chain steps are marked **not estimated**, never an invented zero-credit quote. See [CHANGELOG](CHANGELOG.md). Unknown extra parameter keys remain passthrough, not a guarantee of server validity.
|
|
4
|
+
|
|
3
5
|
Turn a sentence or an image into an engine-ready 3D asset from your terminal, powered by the [Tripo](https://www.tripo3d.ai) V3 API.
|
|
4
6
|
|
|
5
7
|
```bash
|
|
@@ -15,14 +17,14 @@ tripo login # browser approves, key arrives automatically; or --paste /
|
|
|
15
17
|
tripo make knight.png --for game-mobile --then texture,rig,convert:fbx
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
Works with both Tripo regions from the same install — international (`openapi.tripo3d.ai`, console `developers.tripo3d.ai`, email login + Stripe) and China mainland (`openapi.tripo3d.com`, console `developers.tripo3d.com`, SMS login + Alipay). `tripo login` runs an RFC 8628 device flow: the terminal shows a short code, the browser signs in (each console's own method) and approves, and the key lands in the CLI automatically — with graceful fallback to open-the-keys-page-and-paste when the device-auth backend is unavailable. The same flow works without a TTY: `tripo login --region ov|cn` from an agent or CI prints the verification URL + code and waits for the browser approval, so an AI agent can start the login and relay the code to its human. You never pick a region when a key is in hand: the key is probed against both regions and whichever accepts it wins; `tripo topup` then opens the matching billing page (`/billing` on ov, `/billing/recharge` on cn). If a config was moved between machines, `tripo doctor` detects the mismatch and prints the one-line fix.
|
|
20
|
+
Works with both Tripo regions from the same install — international (`openapi.tripo3d.ai`, console `developers.tripo3d.ai`, email login + Stripe) and China mainland (`openapi.tripo3d.com`, console `developers.tripo3d.com`, SMS login + Alipay). `tripo login` runs an RFC 8628 device flow: the terminal shows a short code, the browser signs in (each console's own method) and approves, and the key lands in the CLI automatically — with graceful fallback to open-the-keys-page-and-paste when the device-auth backend is unavailable. The same flow works without a TTY: `tripo login --region ov|cn` from an agent or CI prints the verification URL + code and waits for the browser approval, so an AI agent can start the login and relay the code to its human. Chat agents that cannot keep a command running (e.g. ChatGPT dots) add `--no-wait`: it exits right after printing, and rerunning the same command once the human has approved finishes the login with that same code. You never pick a region when a key is in hand: the key is probed against both regions and whichever accepts it wins; `tripo topup` then opens the matching billing page (`/billing` on ov, `/billing/recharge` on cn). If a config was moved between machines, `tripo doctor` detects the mismatch and prints the one-line fix.
|
|
19
21
|
|
|
20
22
|
## Why this CLI
|
|
21
23
|
|
|
22
24
|
- **One magic command.** `tripo make <anything>` detects what you gave it — prompt text, an image, 2–4 view images, a model file, a URL, a task id, or `@last` — picks the right endpoint, model, and parameters, then waits and downloads the artifacts. No file_token, no polling loops.
|
|
23
|
-
- **A 3D domain brain, not a thin API wrapper.**
|
|
25
|
+
- **A 3D domain brain, not a thin API wrapper.** Explicit `--model` always wins. `tripo-v3.1` (H3.1) is the official default flagship model, and dense geometry, `smart_low_poly=true`, `generate_parts=true`, any explicit `geometry_quality`, or a face budget outside P2's range select it first; a `--for` preset comes next and outranks prompt hints. Otherwise real-time, game, editable, low-poly, voxel, quad, or a face budget within P2's range select `tripo-p2`. `tripo-p1` is an explicit lower-cost option. P2 costs 100–130 credits per generation versus 10–50 for H3.1 before add-ons; the plan card shows the estimate before you run. These are CLI aliases — the wire value the API receives is `P1-20260311` / `v3.1-20260211` / `P2-20260801` (full alias ↔ wire ↔ status table: `tripo docs --topic commands/generate`; official [Models & Versions](https://developers.tripo3d.ai/en/docs/models-and-versions) and [P Series](https://developers.tripo3d.ai/en/models/p1) pages). P-series illegal parameters are stripped locally, quad + GLB is rejected before it costs you credits, and 7 scenario presets (`--for game-mobile|game-pc|film|print|ar-web|anim|toy`) encode sane parameters + processing chains + delivery formats.
|
|
24
26
|
- **You see the plan before you pay.** `tripo make` prints a plan card — one numbered line per API call (endpoint, what it does, its arguments) — and asks before submitting; afterwards it reports the total credits **and** a per-task breakdown. `--yes` (automatic when headless) skips the question but keeps the card in the log.
|
|
25
|
-
- **Agent-native.** Non-TTY runs are automatically headless (`--json --yes --no-open`),
|
|
27
|
+
- **Agent-native.** Non-TTY runs are automatically headless (`--json --yes --no-open`), every result command prints exactly one JSON line on stdout (`docs`/`completion` print text, `mcp` speaks JSON-RPC), progress goes to stderr, exit codes are stable, and JSON pipes compose: `tripo make cat.png | tripo model texture | tripo anim rig`. An LLM-facing skill package ships inside the npm package (`tripo docs --llm`).
|
|
26
28
|
|
|
27
29
|
## Commands
|
|
28
30
|
|
|
@@ -35,8 +37,8 @@ Works with both Tripo regions from the same install — international (`openapi.
|
|
|
35
37
|
| `tripo login / logout / whoami / use` | device flow: browser approves, key arrives automatically (paste fallback); stored 0600 in `~/.tripo/config.json`; `use` switches accounts |
|
|
36
38
|
| `tripo topup / balance / usage` | open the billing page + detect the credits arriving; check spend |
|
|
37
39
|
| `tripo generate <endpoint>` | deterministic access to all 8 generation endpoints |
|
|
38
|
-
| `tripo model / anim / mesh <step>` |
|
|
39
|
-
| `tripo task get/list/watch` | query and
|
|
40
|
+
| `tripo model / anim / mesh <step>` | texture, convert, import, rig-check, rig, retarget, segment, complete, decimate, uv, smartsegment |
|
|
41
|
+
| `tripo task get/list/watch` | query tasks and wait for them (progress on stderr) |
|
|
40
42
|
| `tripo files upload` | upload a file, print the `file_token` |
|
|
41
43
|
| `tripo batch run <manifest.yaml>` | bulk pipelines with concurrency, retries, and resume |
|
|
42
44
|
| `tripo config / doctor` | settings (global + per-directory `.tripo/context.json`) and self-diagnosis |
|
|
@@ -60,15 +62,15 @@ tripo make "a fox" --profile work-cn # one-shot override, nothing sticky
|
|
|
60
62
|
tripo logout # removes the *active* profile only
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
Each profile carries its own key + region, so switching accounts switches regions automatically. `TRIPO_PROFILE=work-cn` does the same as `--profile work-cn`; `TRIPO_API_KEY` bypasses profiles entirely (CI/agents)
|
|
65
|
+
Each profile carries its own key + region, so switching accounts switches regions automatically. `TRIPO_PROFILE=work-cn` does the same as `--profile work-cn`; `TRIPO_API_KEY` bypasses profiles entirely (CI/agents) and is not probed for its region — pair a China-region key with `TRIPO_REGION=cn`. Configs written by older versions are read as the `default` profile — no migration step, nothing breaks.
|
|
64
66
|
|
|
65
67
|
## Scenario presets
|
|
66
68
|
|
|
67
69
|
```bash
|
|
68
|
-
tripo make "sci-fi crate" --for game-mobile #
|
|
70
|
+
tripo make "sci-fi crate" --for game-mobile # P2 editable topology → texture → FBX
|
|
69
71
|
tripo make hero.png --for film # v3.1, quad topology, 4K PBR → USDZ/OBJ
|
|
70
72
|
tripo make "chess knight" --for print # no textures, watertight → STL, flat bottom
|
|
71
|
-
tripo make sofa.jpg --for ar-web #
|
|
73
|
+
tripo make sofa.jpg --for ar-web # P2 capped at 20k faces → GLB + USDZ
|
|
72
74
|
tripo make "orc warrior" --for anim # rig-check gate → rig → FBX
|
|
73
75
|
```
|
|
74
76
|
|
|
@@ -91,17 +93,17 @@ tripo batch run assets.yaml --concurrency 2
|
|
|
91
93
|
|
|
92
94
|
- `make`/`watch` are blocking: run them, wait for exit, read the single JSON line on stdout. Do not poll yourself.
|
|
93
95
|
- The plan card on stderr lists every call before it runs; the final JSON carries `credits_consumed` (total for the run) plus `credits_breakdown` (per task) when a chain ran.
|
|
94
|
-
- Exit codes: `0` ok · `2` usage · `3` auth · `4` insufficient credits · `5` content policy · `6` task failed (
|
|
95
|
-
- `preview.png` in every output directory is your eyes: look at it, then
|
|
96
|
+
- Exit codes: `0` ok · `2` usage · `3` auth · `4` insufficient credits · `5` content policy · `6` task failed (failed/cancelled tasks are auto-refunded; a `timeout` means it is still running) · `7` network (a failed submit is never resent — check `tripo balance` before re-running; a task that succeeded but failed to download carries `task_id` in the error JSON — run `tripo task get <task_id> --download`, never re-make) · `8` not found · `9` rate limit.
|
|
97
|
+
- `preview.png` in every output directory is your eyes: look at it, then continue the chain or ask the human before `tripo redo` (a new paid task with no plan card or confirmation).
|
|
96
98
|
- Full behavior rules: `tripo docs --llm`, per-command docs via `tripo docs --topic commands/make`.
|
|
97
99
|
|
|
98
100
|
## Environment variables
|
|
99
101
|
|
|
100
102
|
| var | meaning |
|
|
101
103
|
| --- | --- |
|
|
102
|
-
| `TRIPO_API_KEY` | API key (`tsk_...`), highest precedence (bypasses profiles) |
|
|
104
|
+
| `TRIPO_API_KEY` | API key (`tsk_...`), highest precedence (bypasses profiles); its region is not probed — set `TRIPO_REGION=cn` for a China-region key |
|
|
103
105
|
| `TRIPO_PROFILE` | account profile to use, same as the global `--profile` flag |
|
|
104
|
-
| `TRIPO_REGION` | `ov` or `cn` —
|
|
106
|
+
| `TRIPO_REGION` | `ov` or `cn` — unnecessary after `tripo login` (it probes both regions with your key and stores the right one); required with a China-region `TRIPO_API_KEY` |
|
|
105
107
|
| `TRIPO_API_BASE_URL` / `TRIPO_PLATFORM_BASE_URL` | endpoint overrides |
|
|
106
108
|
| `TRIPO_HOME` | config/history directory (default `~/.tripo`) |
|
|
107
109
|
| `TRIPO_NO_UPDATE_CHECK` | disable the startup "new version available" hint (also off when `CI` is set, headless, or `--json`) |
|
|
@@ -116,8 +118,8 @@ Setting `TRIPO_API_KEY` per shell — bash/zsh: `export TRIPO_API_KEY=tsk_...`
|
|
|
116
118
|
|
|
117
119
|
Since 0.7.0 the CLI sends usage events to Tripo (`https://sshu-api.tripo3d.ai`) for calls to the international API (`openapi.tripo3d.ai`). It is on by default, needs no setup, prints nothing, and never changes stdout, exit codes, or credit behavior.
|
|
118
120
|
|
|
119
|
-
- **Sent:** command or MCP tool name and its result, sign-in method and result, error category with HTTP status / API code, task id (and parent task id for chains), task type, model, status, credits consumed, durations, downloaded format and file count, CLI version, and the calling agent with its version — `codex` / `workbuddy` / `zcode` / `kimi` / `deepseek`
|
|
120
|
-
- **Identity:** a random installation id (`analytics_distinct_id` in `~/.tripo/config.json`) and, since 0.7.1, your Tripo account id (`platform_user_id`) when it can be resolved. It is looked up for the API key actually in use with `POST https://apiplatform.tripo3d.ai/verify`, once per command or
|
|
121
|
+
- **Sent:** command or MCP tool name and its result, sign-in method and result, error category with HTTP status / API code, task id (and parent task id for chains), task type, model, status, credits consumed, durations, downloaded format and file count, CLI version, and the calling agent with its version — `codex` / `workbuddy` / `zcode` / `kimi` / `deepseek` / `claude` / `cursor` / `chatgpt` (otherwise `unknown`). Explicit `TRIPO_CLIENT_APP` takes priority over the MCP host's client info. When neither identifies the caller, the CLI can recognize Codex (`CODEX_THREAD_ID`), ZCode (`ZCODE_SESSION_ID`), or WorkBuddy (`CODEBUDDY_HOST=workbuddy-desktop`); only the app name is sent, never these marker values. Conflicting host markers remain `unknown`. `TRIPO_DISTRIBUTION_CHANNEL` names the plugin separately and is never used to guess the host; attribution is resolved per invocation and is not cached against an account or installation.
|
|
122
|
+
- **Identity:** a random installation id (`analytics_distinct_id` in `~/.tripo/config.json`) and, since 0.7.1, your Tripo account id (`platform_user_id`) when it can be resolved. It is looked up for the API key actually in use with `POST https://apiplatform.tripo3d.ai/verify`, at most once per command or per `tripo mcp` session (since 0.7.3 a session reuses it across tool calls), alongside the first API request rather than before it; events wait at most 1 second for it, the lookup is never retried within that command or session, and the id is kept only in memory — never written to disk or printed — so on failure events simply go without it. No email or billing details are sent.
|
|
121
123
|
- **Never sent:** prompts, file paths, input images or models, API keys, download URLs (the key itself only goes to `/verify` for the account lookup).
|
|
122
124
|
- **Nothing is sent, and no account lookup is made, for:** China-region (`cn`) calls, custom API endpoints, `--dry-run`, help/version, `docs`, `completion`, `config`, `update`, `use`, `logout`, and the bare `tripo` home screen.
|
|
123
125
|
- **Opt out:** `TRIPO_ANALYTICS_ENABLED=0` (also skips the account lookup).
|
package/dist/ai/experts.js
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
* knowledge base (scenario ids, step names, parameter windows), and the
|
|
7
7
|
* result is re-validated locally before anything is spent.
|
|
8
8
|
*/
|
|
9
|
-
import { getLocale, t, tList } from '../i18n.js';
|
|
10
9
|
import { availableSteps, parseThenExpression, validateChain } from '../knowledge/chains.js';
|
|
11
|
-
import { chooseModel, detectLowPolyIntent,
|
|
10
|
+
import { CAPABILITY_DEFAULTS, chooseModel, detectDenseGeometryIntent, detectLowPolyIntent, detectRealtimeIntent } from '../knowledge/models.js';
|
|
12
11
|
import { getScenario, SCENARIOS } from '../knowledge/scenarios.js';
|
|
13
12
|
import { chatJson } from './llm.js';
|
|
14
|
-
const SCENARIO_TABLE = SCENARIOS.map((s) => `- ${s.id}: ${s.description
|
|
13
|
+
const SCENARIO_TABLE = SCENARIOS.map((s) => `- ${s.id}: ${s.description} | model=${s.model} | chain=${s.chain.map((c) => c.step).join('→') || 'none'} | deliver=${s.deliverables.join('/')}`).join('\n');
|
|
15
14
|
const STEP_TABLE = availableSteps().map((s) => s.name).join(', ');
|
|
16
15
|
const PRODUCER_SYSTEM = `You are the producer/router of the Tripo 3D generation CLI.
|
|
17
16
|
Classify the user's request into exactly one scenario and extract a clean English 3D generation prompt.
|
|
@@ -23,30 +22,30 @@ Rules:
|
|
|
23
22
|
- "prompt" must describe the object to generate (translate to English, keep it concrete, <= 200 chars).
|
|
24
23
|
- Set "face_limit" only when the user states a face/polygon budget.
|
|
25
24
|
- Set "needs_animation" true only when the user wants skeleton/animations.
|
|
26
|
-
- If one critical piece of information is missing and materially changes the plan, set "question" (one short question in
|
|
25
|
+
- If one critical piece of information is missing and materially changes the plan, set "question" (one short question in English). Otherwise omit it.
|
|
27
26
|
Respond with JSON only: {"scenario","prompt","face_limit","needs_animation","question","notes"}`;
|
|
28
27
|
const SPECIALIST_PROMPTS = {
|
|
29
|
-
'game-mobile': `You are a senior game technical artist (mobile).
|
|
30
|
-
'game-pc': `You are a senior game technical artist (PC/console).
|
|
28
|
+
'game-mobile': `You are a senior game technical artist (mobile). Prefer P2 editable topology for 48–25000 quad or 48–50000 triangle faces, 1–2K textures, and FBX/GLB delivery. Call out P2's higher credit cost.`,
|
|
29
|
+
'game-pc': `You are a senior game technical artist (PC/console). Prefer P2 editable topology with detailed PBR textures, optional decimate for LODs (bake=true), and FBX delivery with engine presets. Use H only when dense geometry or H-only parameters are required. Call out P2's higher credit cost.`,
|
|
31
30
|
film: `You are a film/offline-rendering artist. quad=true subdivision-friendly topology (FBX output), geometry_quality=detailed, texture_quality=detailed, 4K textures, USD/OBJ handoff via convert.`,
|
|
32
|
-
print: `You are a 3D-printing engineer. Geometry only (texture=false,pbr=false), watertight meshes, flatten_bottom for FDM, STL
|
|
33
|
-
'ar-web': `You are a realtime AR/Web 3D engineer.
|
|
34
|
-
anim: `You are a character animation TD. Chain: rig-check → rig (
|
|
35
|
-
toy: `You are a stylized collectible designer.
|
|
31
|
+
print: `You are a 3D-printing engineer. Geometry only (texture=false,pbr=false), watertight meshes, flatten_bottom for FDM, STL via convert (API 3MF output is single color; for color prints set texture=true and convert to OBJ with export_vertex_colors=true), real-world sizing via auto_size at generation or scale_factor at convert.`,
|
|
32
|
+
'ar-web': `You are a realtime AR/Web 3D engineer. Prefer P2 editable topology, keep faces <= 20000 with face_limit at generation (no decimate step), use 2K textures, and deliver GLB + USDZ (iOS QuickLook). Call out P2's higher credit cost.`,
|
|
33
|
+
anim: `You are a character animation TD. Prefer P2 editable topology. Chain: rig-check → rig (v3.0 rig, spec tripo|mixamo|actorcore|unity|unreal|vrm, biped only) → retarget (<=5 preset animations, billed per animation). Mixamo → spec=mixamo; Unity Humanoid → spec=unity; Unreal → spec=unreal. Call out P2's higher credit cost.`,
|
|
34
|
+
toy: `You are a stylized collectible designer. Prefer P2 editable low-poly topology; use STL via convert for physical prints. Call out P2's higher credit cost.`,
|
|
36
35
|
};
|
|
37
36
|
function specialistSystem(scenarioId) {
|
|
38
37
|
const persona = SPECIALIST_PROMPTS[scenarioId] ?? SPECIALIST_PROMPTS['game-mobile'];
|
|
39
38
|
return `${persona}
|
|
40
39
|
You fine-tune a Tripo generation plan. You may adjust generation params and the processing chain.
|
|
41
40
|
Valid chain steps: ${STEP_TABLE}. Chain syntax: "step:key=value,step2" (e.g. "texture,rig,convert:format=FBX").
|
|
42
|
-
Constraints: pbr=>texture; generate_parts excludes texture/pbr/quad;
|
|
43
|
-
Respond with JSON only: {"params":{...},"chain":"...","notes":["short practical notes in
|
|
41
|
+
Constraints: pbr=>texture; generate_parts excludes texture/pbr/quad; P2 face_limit is 48-50000 triangles or 48-25000 quads and forbids smart_low_poly/generate_parts/geometry_quality; retarget <= 5 animations.
|
|
42
|
+
Respond with JSON only: {"params":{...},"chain":"...","notes":["short practical notes in English"]}. Omit fields you don't want to change.`;
|
|
44
43
|
}
|
|
45
44
|
/**
|
|
46
45
|
* Run the expert pipeline. `askUser` is called when the producer needs one
|
|
47
46
|
* clarification (interactive mode only); return undefined to skip.
|
|
48
47
|
*/
|
|
49
|
-
export async function buildExpertPlan(request, askUser) {
|
|
48
|
+
export async function buildExpertPlan(request, askUser, overrides = {}) {
|
|
50
49
|
const messages = [
|
|
51
50
|
{ role: 'system', content: PRODUCER_SYSTEM },
|
|
52
51
|
{ role: 'user', content: request },
|
|
@@ -69,9 +68,12 @@ export async function buildExpertPlan(request, askUser) {
|
|
|
69
68
|
},
|
|
70
69
|
]).catch(() => ({}));
|
|
71
70
|
// Merge + re-validate everything the LLM produced.
|
|
72
|
-
const params = {
|
|
73
|
-
|
|
74
|
-
params
|
|
71
|
+
const params = {
|
|
72
|
+
...scenario.genParams,
|
|
73
|
+
...(specialist.params ?? {}),
|
|
74
|
+
...(producer.face_limit === undefined ? {} : { face_limit: producer.face_limit }),
|
|
75
|
+
...overrides,
|
|
76
|
+
};
|
|
75
77
|
let chain = scenario.chain;
|
|
76
78
|
if (specialist.chain) {
|
|
77
79
|
try {
|
|
@@ -85,14 +87,18 @@ export async function buildExpertPlan(request, askUser) {
|
|
|
85
87
|
chain = [
|
|
86
88
|
...chain.filter((s) => s.step !== 'convert'),
|
|
87
89
|
{ step: 'rig-check', args: {} },
|
|
88
|
-
{ step: 'rig', args: { model:
|
|
90
|
+
{ step: 'rig', args: { model: CAPABILITY_DEFAULTS.rig } },
|
|
89
91
|
{ step: 'retarget', args: { animations: ['preset:idle', 'preset:walk'] } },
|
|
90
92
|
];
|
|
91
93
|
}
|
|
92
94
|
const choice = chooseModel({
|
|
93
|
-
|
|
95
|
+
preferred: scenario.model,
|
|
94
96
|
faceLimit: typeof params.face_limit === 'number' ? params.face_limit : undefined,
|
|
97
|
+
quad: params.quad === true,
|
|
95
98
|
lowPoly: detectLowPolyIntent(request),
|
|
99
|
+
realtime: detectRealtimeIntent(request),
|
|
100
|
+
denseGeometry: detectDenseGeometryIntent(request),
|
|
101
|
+
params,
|
|
96
102
|
});
|
|
97
103
|
try {
|
|
98
104
|
validateChain(chain, 'text_to_model');
|
|
@@ -104,11 +110,11 @@ export async function buildExpertPlan(request, askUser) {
|
|
|
104
110
|
inputSummary: producer.prompt,
|
|
105
111
|
scenario: scenario.id,
|
|
106
112
|
model: choice.model,
|
|
107
|
-
modelReason:
|
|
113
|
+
modelReason: 'producer routing + scenario preset',
|
|
108
114
|
genParams: params,
|
|
109
115
|
chain,
|
|
110
116
|
deliverables: scenario.deliverables,
|
|
111
|
-
notes:
|
|
117
|
+
notes: scenario.notes,
|
|
112
118
|
expertNotes: [...(producer.notes ?? []), ...(specialist.notes ?? [])],
|
|
113
119
|
};
|
|
114
120
|
return { card, scenarioId: scenario.id, prompt: producer.prompt, chain, params };
|
package/dist/ai/plan-card.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* conversation mode. Decisions come from the knowledge base either way.
|
|
5
5
|
*/
|
|
6
6
|
import pc from 'picocolors';
|
|
7
|
-
import {
|
|
7
|
+
import { estimateGenerationCredits, isP2, normalizeModel } from '../knowledge/models.js';
|
|
8
|
+
import { buildStepRequest } from '../core/requests.js';
|
|
8
9
|
import { getStep } from '../knowledge/chains.js';
|
|
10
|
+
import { isComplexConvert } from '../knowledge/params.js';
|
|
9
11
|
import { getScenario } from '../knowledge/scenarios.js';
|
|
10
12
|
const GENERATION_DESCRIBE = {
|
|
11
|
-
text_to_model:
|
|
12
|
-
image_to_model:
|
|
13
|
-
multiview_to_model:
|
|
13
|
+
text_to_model: 'generate a 3D model from text',
|
|
14
|
+
image_to_model: 'generate a 3D model from one image',
|
|
15
|
+
multiview_to_model: 'generate a 3D model from multiple views',
|
|
14
16
|
};
|
|
15
17
|
/** Keys that describe the input itself (already shown on the "input" line). */
|
|
16
18
|
const INPUT_KEYS = new Set(['prompt', 'input', 'inputs']);
|
|
@@ -18,6 +20,8 @@ const INPUT_KEYS = new Set(['prompt', 'input', 'inputs']);
|
|
|
18
20
|
export function cardFromMakePlan(plan, opts) {
|
|
19
21
|
const steps = [];
|
|
20
22
|
const genParams = {};
|
|
23
|
+
const notes = [...plan.warnings];
|
|
24
|
+
const chain = [];
|
|
21
25
|
if (plan.generation) {
|
|
22
26
|
const { taskType, request } = plan.generation;
|
|
23
27
|
const args = {};
|
|
@@ -32,26 +36,46 @@ export function cardFromMakePlan(plan, opts) {
|
|
|
32
36
|
steps.push({
|
|
33
37
|
name: importDef ? importDef.name : taskType.replace(/_/g, '-'),
|
|
34
38
|
endpoint: request.endpoint,
|
|
35
|
-
describe: importDef ?
|
|
39
|
+
describe: importDef ? importDef.describe : GENERATION_DESCRIBE[taskType] ?? 'generate',
|
|
36
40
|
args,
|
|
37
41
|
});
|
|
38
42
|
}
|
|
39
43
|
for (const spec of plan.chain) {
|
|
40
44
|
const def = getStep(spec.step);
|
|
45
|
+
const built = buildStepRequest(spec.step, '<upstream>', spec.args);
|
|
46
|
+
const args = { ...built.request.payload };
|
|
47
|
+
delete args.input;
|
|
48
|
+
notes.push(...built.warnings);
|
|
49
|
+
chain.push({ step: def?.name ?? spec.step, args });
|
|
41
50
|
steps.push({
|
|
42
51
|
name: spec.step,
|
|
43
52
|
endpoint: def?.endpoint ?? '',
|
|
44
|
-
describe: def ?
|
|
45
|
-
args
|
|
53
|
+
describe: def ? def.describe : '',
|
|
54
|
+
args,
|
|
46
55
|
});
|
|
47
56
|
}
|
|
48
57
|
const scenario = opts.scenario ? getScenario(opts.scenario) : undefined;
|
|
49
|
-
|
|
58
|
+
// Static tips are valid only when the scenario's assumptions survived normalization.
|
|
59
|
+
if (scenario && plan.model && normalizeModel(plan.model) === normalizeModel(scenario.model) &&
|
|
60
|
+
Object.entries(scenario.genParams).every(([key, value]) => genParams[key] === value) &&
|
|
61
|
+
JSON.stringify(plan.chain) === JSON.stringify(scenario.chain)) {
|
|
62
|
+
notes.unshift(...scenario.notes);
|
|
63
|
+
}
|
|
64
|
+
// Advertise actual selected outputs, never optional scenario formats or removed quads.
|
|
65
|
+
const generated = plan.generation && Object.hasOwn(GENERATION_DESCRIBE, plan.generation.taskType)
|
|
66
|
+
? [genParams.quad === true ? 'FBX' : 'GLB'] : [];
|
|
67
|
+
let deliverables = generated;
|
|
68
|
+
for (const spec of chain) {
|
|
69
|
+
if (getStep(spec.step)?.passthrough)
|
|
70
|
+
continue;
|
|
71
|
+
const format = spec.step === 'convert' ? (spec.args.quad === true ? 'FBX' : spec.args.format)
|
|
72
|
+
: spec.step === 'rig' ? spec.args.out_format : undefined;
|
|
73
|
+
deliverables = typeof format === 'string' ? [format.toUpperCase()] : [];
|
|
74
|
+
}
|
|
75
|
+
if (plan.downloadGeneration)
|
|
76
|
+
deliverables = [...new Set([...generated, ...deliverables])];
|
|
50
77
|
if (opts.candidates && opts.candidates > 1) {
|
|
51
|
-
notes.push(
|
|
52
|
-
zh: `并行生成 ${opts.candidates} 个候选(不同 seed),选中一个后再继续加工链`,
|
|
53
|
-
en: `${opts.candidates} candidates in parallel (distinct seeds); pick one before the chain continues`,
|
|
54
|
-
}));
|
|
78
|
+
notes.push(`${opts.candidates} candidates in parallel (distinct seeds); pick one before the chain continues`);
|
|
55
79
|
}
|
|
56
80
|
// Task refs (@last / @name) resolve to an id the human has not seen yet — show it.
|
|
57
81
|
let inputSummary = opts.inputs.join(' ');
|
|
@@ -63,39 +87,54 @@ export function cardFromMakePlan(plan, opts) {
|
|
|
63
87
|
scenario: opts.scenario,
|
|
64
88
|
model: plan.model,
|
|
65
89
|
modelReason: plan.modelReason,
|
|
90
|
+
taskType: plan.generation?.taskType,
|
|
91
|
+
candidates: opts.candidates,
|
|
66
92
|
genParams,
|
|
67
|
-
chain
|
|
68
|
-
deliverables
|
|
69
|
-
notes,
|
|
93
|
+
chain,
|
|
94
|
+
deliverables,
|
|
95
|
+
notes: [...new Set(notes)],
|
|
70
96
|
steps,
|
|
71
97
|
};
|
|
72
98
|
}
|
|
99
|
+
/** List-price credits for the generation step × candidates; undefined when no official price applies. */
|
|
100
|
+
export function generationCreditsEstimate(card) {
|
|
101
|
+
if (!card.model || !card.taskType)
|
|
102
|
+
return undefined;
|
|
103
|
+
const unit = estimateGenerationCredits(card.model, card.taskType, card.genParams);
|
|
104
|
+
return unit === undefined ? undefined : unit * (card.candidates && card.candidates > 1 ? card.candidates : 1);
|
|
105
|
+
}
|
|
73
106
|
/** Cost-impact annotations derived from the parameter set. */
|
|
74
107
|
export function costNotes(card) {
|
|
75
|
-
const
|
|
108
|
+
const total = generationCreditsEstimate(card);
|
|
109
|
+
const n = card.candidates && card.candidates > 1 ? card.candidates : 1;
|
|
110
|
+
const amount = n > 1 && total !== undefined ? `${n} × ${total / n} = ${total}` : String(total);
|
|
111
|
+
const chainNote = card.chain.length > 0;
|
|
112
|
+
const notes = [total === undefined ? 'cost not estimated (not zero credits); notes below are cost impacts only; check regional official pricing and balance before running' : `estimated generation cost: ${amount} credits (official list price${chainNote ? '; chain steps not estimated' : ''})`];
|
|
76
113
|
const params = card.genParams;
|
|
77
114
|
if (params.texture === false) {
|
|
78
|
-
notes.push(
|
|
115
|
+
notes.push('bare geometry: no texture credits');
|
|
79
116
|
}
|
|
80
117
|
if (params.texture_quality === 'detailed' || params.texture_quality === 'extreme') {
|
|
81
|
-
notes.push(
|
|
118
|
+
notes.push(`texture_quality ${params.texture_quality}: higher price tier`);
|
|
82
119
|
}
|
|
83
120
|
if (params.geometry_quality === 'detailed') {
|
|
84
|
-
notes.push(
|
|
121
|
+
notes.push('geometry detailed (Ultra): extra HD credit');
|
|
85
122
|
}
|
|
86
123
|
if (params.quad === true) {
|
|
87
|
-
notes.push(
|
|
124
|
+
notes.push(card.model && isP2(card.model)
|
|
125
|
+
? 'quad topology: FBX output'
|
|
126
|
+
: 'quad topology: higher price tier, FBX output');
|
|
88
127
|
}
|
|
89
128
|
if (params.generate_parts === true) {
|
|
90
|
-
notes.push(
|
|
129
|
+
notes.push('part generation: higher price tier, bare geometry');
|
|
91
130
|
}
|
|
92
131
|
for (const step of card.chain) {
|
|
93
|
-
if (step.step === 'convert' &&
|
|
94
|
-
notes.push(
|
|
132
|
+
if (step.step === 'convert' && isComplexConvert(step.args)) {
|
|
133
|
+
notes.push('non-default convert options bill as complex convert');
|
|
95
134
|
}
|
|
96
135
|
if (step.step === 'retarget') {
|
|
97
136
|
const count = Array.isArray(step.args.animations) ? step.args.animations.length : 1;
|
|
98
|
-
notes.push(
|
|
137
|
+
notes.push(`retarget ${count} animation(s): billed per animation`);
|
|
99
138
|
}
|
|
100
139
|
}
|
|
101
140
|
return notes;
|
|
@@ -104,18 +143,18 @@ export function costNotes(card) {
|
|
|
104
143
|
export function renderPlanCard(card) {
|
|
105
144
|
const scenario = card.scenario ? getScenario(card.scenario) : undefined;
|
|
106
145
|
const lines = [];
|
|
107
|
-
const title =
|
|
146
|
+
const title = 'Plan';
|
|
108
147
|
lines.push(pc.bold(`── ${title} ──────────────────────────`));
|
|
109
|
-
lines.push(`${
|
|
148
|
+
lines.push(`${'input'}: ${card.inputSummary}`);
|
|
110
149
|
if (scenario)
|
|
111
|
-
lines.push(`${
|
|
150
|
+
lines.push(`${'scenario'}: ${scenario.id} (${scenario.label})`);
|
|
112
151
|
if (card.model) {
|
|
113
|
-
lines.push(`${
|
|
152
|
+
lines.push(`${'model'}: ${card.model}${card.modelReason ? pc.dim(` — ${card.modelReason}`) : ''}`);
|
|
114
153
|
}
|
|
115
154
|
const fmtArgs = (args) => Object.entries(args).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(' ');
|
|
116
155
|
if (card.steps && card.steps.length > 0) {
|
|
117
156
|
// Numbered step list: what each call does, which endpoint, with which args.
|
|
118
|
-
lines.push(`${
|
|
157
|
+
lines.push(`${'steps'}:`);
|
|
119
158
|
card.steps.forEach((step, i) => {
|
|
120
159
|
lines.push(` ${i + 1}. ${step.name} ${pc.dim(`POST ${step.endpoint}`)}${step.describe ? ` — ${step.describe}` : ''}`);
|
|
121
160
|
if (Object.keys(step.args).length > 0)
|
|
@@ -125,28 +164,28 @@ export function renderPlanCard(card) {
|
|
|
125
164
|
else {
|
|
126
165
|
const params = Object.entries(card.genParams);
|
|
127
166
|
if (params.length > 0) {
|
|
128
|
-
lines.push(`${
|
|
167
|
+
lines.push(`${'params'}: ${fmtArgs(card.genParams)}`);
|
|
129
168
|
}
|
|
130
169
|
if (card.chain.length > 0) {
|
|
131
170
|
const chainStr = card.chain
|
|
132
171
|
.map((s) => (Object.keys(s.args).length > 0 ? `${s.step}(${Object.entries(s.args).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(',')})` : s.step))
|
|
133
172
|
.join(' → ');
|
|
134
|
-
lines.push(`${
|
|
173
|
+
lines.push(`${'chain'}: ${chainStr}`);
|
|
135
174
|
}
|
|
136
175
|
}
|
|
137
176
|
if (card.deliverables.length > 0) {
|
|
138
|
-
lines.push(`${
|
|
177
|
+
lines.push(`${'deliverables'}: ${card.deliverables.join(', ')}`);
|
|
139
178
|
}
|
|
140
179
|
const cost = costNotes(card);
|
|
141
180
|
if (cost.length > 0) {
|
|
142
|
-
lines.push(`${
|
|
181
|
+
lines.push(`${'cost impact'}:`);
|
|
143
182
|
for (const note of cost)
|
|
144
183
|
lines.push(` · ${note}`);
|
|
145
184
|
}
|
|
146
185
|
for (const note of card.notes)
|
|
147
186
|
lines.push(pc.dim(` ${note}`));
|
|
148
187
|
if (card.expertNotes && card.expertNotes.length > 0) {
|
|
149
|
-
lines.push(`${
|
|
188
|
+
lines.push(`${'expert notes'}:`);
|
|
150
189
|
for (const note of card.expertNotes)
|
|
151
190
|
lines.push(` · ${note}`);
|
|
152
191
|
}
|
package/dist/ai/wizard.js
CHANGED
|
@@ -6,31 +6,27 @@
|
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
7
|
import { CliError, UsageError } from '../core/errors.js';
|
|
8
8
|
import { isInteractive } from '../core/output.js';
|
|
9
|
-
import { t, tList } from '../i18n.js';
|
|
10
9
|
import { EXIT } from '../knowledge/error-catalog.js';
|
|
11
|
-
import {
|
|
10
|
+
import { CAPABILITY_DEFAULTS } from '../knowledge/models.js';
|
|
12
11
|
import { getScenario, SCENARIOS } from '../knowledge/scenarios.js';
|
|
13
12
|
export async function runWizard(initialPrompt) {
|
|
14
13
|
if (!isInteractive()) {
|
|
15
|
-
throw new UsageError(
|
|
16
|
-
zh: '向导需要交互终端;无头环境请直接用 tripo make --for <场景> 或配置 LLM 后用 tripo ai --yes',
|
|
17
|
-
en: 'the wizard needs an interactive terminal; headless callers should use "tripo make --for <scenario>" instead',
|
|
18
|
-
}));
|
|
14
|
+
throw new UsageError('the wizard needs an interactive terminal; headless callers should use "tripo make --for <scenario>" instead');
|
|
19
15
|
}
|
|
20
16
|
p.intro('tripo ai');
|
|
21
17
|
let prompt = initialPrompt?.trim() ?? '';
|
|
22
18
|
if (!prompt) {
|
|
23
19
|
const answer = await p.text({
|
|
24
|
-
message:
|
|
25
|
-
validate: (v) => (v && v.trim().length > 0 ? undefined :
|
|
20
|
+
message: 'What do you want to make? one sentence (or an image path)',
|
|
21
|
+
validate: (v) => (v && v.trim().length > 0 ? undefined : 'say something'),
|
|
26
22
|
});
|
|
27
23
|
if (p.isCancel(answer))
|
|
28
24
|
throw new CliError('cancelled', EXIT.FATAL);
|
|
29
25
|
prompt = answer.trim();
|
|
30
26
|
}
|
|
31
27
|
const scenarioAnswer = await p.select({
|
|
32
|
-
message:
|
|
33
|
-
options: SCENARIOS.map((s) => ({ value: s.id, label: `${
|
|
28
|
+
message: 'What is it for?',
|
|
29
|
+
options: SCENARIOS.map((s) => ({ value: s.id, label: `${s.label} — ${s.description}` })),
|
|
34
30
|
});
|
|
35
31
|
if (p.isCancel(scenarioAnswer))
|
|
36
32
|
throw new CliError('cancelled', EXIT.FATAL);
|
|
@@ -38,12 +34,12 @@ export async function runWizard(initialPrompt) {
|
|
|
38
34
|
let engine;
|
|
39
35
|
if (scenarioAnswer === 'game-mobile' || scenarioAnswer === 'game-pc') {
|
|
40
36
|
const engineAnswer = await p.select({
|
|
41
|
-
message:
|
|
37
|
+
message: 'Target engine/software?',
|
|
42
38
|
options: [
|
|
43
39
|
{ value: 'unity', label: 'Unity' },
|
|
44
40
|
{ value: 'unreal', label: 'Unreal' },
|
|
45
41
|
{ value: 'blender', label: 'Blender' },
|
|
46
|
-
{ value: 'any', label:
|
|
42
|
+
{ value: 'any', label: 'not sure / generic' },
|
|
47
43
|
],
|
|
48
44
|
});
|
|
49
45
|
if (p.isCancel(engineAnswer))
|
|
@@ -53,7 +49,7 @@ export async function runWizard(initialPrompt) {
|
|
|
53
49
|
let animations = false;
|
|
54
50
|
if (scenarioAnswer !== 'print' && scenarioAnswer !== 'film') {
|
|
55
51
|
const animAnswer = await p.confirm({
|
|
56
|
-
message:
|
|
52
|
+
message: 'Do you need a skeleton / animations?',
|
|
57
53
|
initialValue: scenarioAnswer === 'anim',
|
|
58
54
|
});
|
|
59
55
|
if (p.isCancel(animAnswer))
|
|
@@ -65,11 +61,11 @@ export async function runWizard(initialPrompt) {
|
|
|
65
61
|
inputSummary: prompt,
|
|
66
62
|
scenario: scenario.id,
|
|
67
63
|
model: scenario.model,
|
|
68
|
-
modelReason:
|
|
64
|
+
modelReason: 'scenario preset',
|
|
69
65
|
genParams: scenario.genParams,
|
|
70
66
|
chain,
|
|
71
67
|
deliverables: scenario.deliverables,
|
|
72
|
-
notes: [...
|
|
68
|
+
notes: [...scenario.notes, ...notes],
|
|
73
69
|
};
|
|
74
70
|
return { card, options: { prompt, scenario: scenario.id, chain } };
|
|
75
71
|
}
|
|
@@ -79,10 +75,14 @@ export function adjustChain(scenarioId, engine, animations) {
|
|
|
79
75
|
let chain = scenario.chain.map((s) => ({ step: s.step, args: { ...s.args } }));
|
|
80
76
|
const notes = [];
|
|
81
77
|
if (animations && !chain.some((s) => s.step === 'rig')) {
|
|
82
|
-
const rigArgs = { model:
|
|
78
|
+
const rigArgs = { model: CAPABILITY_DEFAULTS.rig, out_format: 'fbx' };
|
|
83
79
|
if (engine === 'unity') {
|
|
84
|
-
rigArgs.spec = '
|
|
85
|
-
notes.push(
|
|
80
|
+
rigArgs.spec = 'unity';
|
|
81
|
+
notes.push('Unity Humanoid → spec=unity');
|
|
82
|
+
}
|
|
83
|
+
else if (engine === 'unreal') {
|
|
84
|
+
rigArgs.spec = 'unreal';
|
|
85
|
+
notes.push('Unreal Mannequin → spec=unreal');
|
|
86
86
|
}
|
|
87
87
|
chain = [
|
|
88
88
|
...chain.filter((s) => s.step !== 'convert'),
|
|
@@ -90,7 +90,7 @@ export function adjustChain(scenarioId, engine, animations) {
|
|
|
90
90
|
{ step: 'rig', args: rigArgs },
|
|
91
91
|
{ step: 'retarget', args: { animations: ['preset:idle', 'preset:walk'], out_format: 'fbx' } },
|
|
92
92
|
];
|
|
93
|
-
notes.push(
|
|
93
|
+
notes.push('animation chain outputs FBX (skeleton + animations)');
|
|
94
94
|
}
|
|
95
95
|
if (engine && !animations) {
|
|
96
96
|
const convert = chain.find((s) => s.step === 'convert');
|
package/dist/commands/account.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
/** `tripo balance` / `tripo usage` — account visibility. */
|
|
2
2
|
import { TripoClient } from '../core/client.js';
|
|
3
3
|
import { emitResult } from '../core/output.js';
|
|
4
|
-
import { t } from '../i18n.js';
|
|
5
4
|
export function registerAccountCommands(program) {
|
|
6
5
|
program
|
|
7
6
|
.command('balance')
|
|
8
7
|
.description('show available and frozen credits')
|
|
9
8
|
.action(async () => {
|
|
10
9
|
const data = await new TripoClient().getBalance();
|
|
11
|
-
emitResult(data,
|
|
12
|
-
zh: `余额 ${data.balance} 积分(冻结 ${data.frozen})`,
|
|
13
|
-
en: `balance: ${data.balance} credits (${data.frozen} frozen)`,
|
|
14
|
-
}));
|
|
10
|
+
emitResult(data, `balance: ${data.balance} credits (${data.frozen} frozen)`);
|
|
15
11
|
});
|
|
16
12
|
program
|
|
17
13
|
.command('usage')
|
|
@@ -22,6 +18,6 @@ export function registerAccountCommands(program) {
|
|
|
22
18
|
const human = items
|
|
23
19
|
.map((i) => `${(i.created_at ?? '').slice(0, 19).padEnd(20)} ${i.type.padEnd(24)} ${String(i.credits_consumed ?? 0).padStart(6)} cr ${i.task_id}`)
|
|
24
20
|
.join('\n');
|
|
25
|
-
emitResult(items, human ||
|
|
21
|
+
emitResult(items, human || 'no usage yet');
|
|
26
22
|
});
|
|
27
23
|
}
|