tripo-cli 0.7.3 → 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 +9 -7
- package/dist/ai/experts.js +25 -19
- package/dist/ai/plan-card.js +71 -33
- package/dist/ai/wizard.js +13 -17
- package/dist/commands/account.js +2 -6
- package/dist/commands/ai.js +18 -16
- package/dist/commands/batch.js +58 -18
- package/dist/commands/config-cmd.js +1 -1
- package/dist/commands/doctor.js +1 -3
- package/dist/commands/generate.js +35 -4
- package/dist/commands/home.js +12 -13
- package/dist/commands/login.js +72 -187
- package/dist/commands/make.js +47 -25
- package/dist/commands/mcp.js +2 -1
- package/dist/commands/process-groups.js +3 -4
- package/dist/commands/shared.js +9 -14
- package/dist/commands/task.js +10 -11
- package/dist/commands/topup.js +6 -10
- package/dist/commands/update.js +6 -10
- package/dist/commands/view.js +3 -4
- package/dist/config/config.js +1 -1
- package/dist/core/client.js +4 -13
- package/dist/core/errors.js +16 -11
- package/dist/core/output.js +0 -4
- package/dist/core/requests.js +61 -17
- package/dist/core/task-service.js +19 -20
- package/dist/knowledge/chains.js +12 -12
- package/dist/knowledge/error-catalog.js +32 -32
- package/dist/knowledge/models.js +119 -20
- package/dist/knowledge/params.js +96 -8
- package/dist/knowledge/scenarios.js +32 -55
- package/dist/pipeline/make.js +36 -28
- package/dist/pipeline/runner.js +7 -12
- package/dist/pipeline/stdin.js +40 -3
- package/dist/program.js +3 -10
- package/package.json +3 -2
- package/skill/SKILL.md +34 -8
- package/skill/commands/account.md +1 -1
- package/skill/commands/batch.md +13 -3
- package/skill/commands/generate.md +35 -10
- package/skill/commands/make.md +39 -13
- package/skill/commands/process.md +2 -1
- package/skill/commands/task.md +2 -2
- package/skill/commands/view.md +1 -1
- package/skill/common-errors.md +6 -3
- package/skill/examples/animation.md +3 -1
- package/skill/examples/ar-web.md +6 -4
- package/skill/examples/game-asset.md +17 -6
- 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
|
|
@@ -20,9 +22,9 @@ Works with both Tripo regions from the same install — international (`openapi.
|
|
|
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
|
|
|
@@ -36,7 +38,7 @@ Works with both Tripo regions from the same install — international (`openapi.
|
|
|
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
40
|
| `tripo model / anim / mesh <step>` | texture, convert, import, rig-check, rig, retarget, segment, complete, decimate, uv, smartsegment |
|
|
39
|
-
| `tripo task get/list/watch` | query and
|
|
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 |
|
|
@@ -65,10 +67,10 @@ Each profile carries its own key + region, so switching accounts switches region
|
|
|
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,8 +93,8 @@ 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 (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) · `8` not found · `9` rate limit.
|
|
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
|
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 { CAPABILITY_DEFAULTS, chooseModel, detectLowPolyIntent } from '../knowledge/models.js';
|
|
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 (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.`,
|
|
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 {
|
|
@@ -90,9 +92,13 @@ export async function buildExpertPlan(request, askUser) {
|
|
|
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,14 +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';
|
|
9
10
|
import { isComplexConvert } from '../knowledge/params.js';
|
|
10
11
|
import { getScenario } from '../knowledge/scenarios.js';
|
|
11
12
|
const GENERATION_DESCRIBE = {
|
|
12
|
-
text_to_model:
|
|
13
|
-
image_to_model:
|
|
14
|
-
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',
|
|
15
16
|
};
|
|
16
17
|
/** Keys that describe the input itself (already shown on the "input" line). */
|
|
17
18
|
const INPUT_KEYS = new Set(['prompt', 'input', 'inputs']);
|
|
@@ -19,6 +20,8 @@ const INPUT_KEYS = new Set(['prompt', 'input', 'inputs']);
|
|
|
19
20
|
export function cardFromMakePlan(plan, opts) {
|
|
20
21
|
const steps = [];
|
|
21
22
|
const genParams = {};
|
|
23
|
+
const notes = [...plan.warnings];
|
|
24
|
+
const chain = [];
|
|
22
25
|
if (plan.generation) {
|
|
23
26
|
const { taskType, request } = plan.generation;
|
|
24
27
|
const args = {};
|
|
@@ -33,26 +36,46 @@ export function cardFromMakePlan(plan, opts) {
|
|
|
33
36
|
steps.push({
|
|
34
37
|
name: importDef ? importDef.name : taskType.replace(/_/g, '-'),
|
|
35
38
|
endpoint: request.endpoint,
|
|
36
|
-
describe: importDef ?
|
|
39
|
+
describe: importDef ? importDef.describe : GENERATION_DESCRIBE[taskType] ?? 'generate',
|
|
37
40
|
args,
|
|
38
41
|
});
|
|
39
42
|
}
|
|
40
43
|
for (const spec of plan.chain) {
|
|
41
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 });
|
|
42
50
|
steps.push({
|
|
43
51
|
name: spec.step,
|
|
44
52
|
endpoint: def?.endpoint ?? '',
|
|
45
|
-
describe: def ?
|
|
46
|
-
args
|
|
53
|
+
describe: def ? def.describe : '',
|
|
54
|
+
args,
|
|
47
55
|
});
|
|
48
56
|
}
|
|
49
57
|
const scenario = opts.scenario ? getScenario(opts.scenario) : undefined;
|
|
50
|
-
|
|
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])];
|
|
51
77
|
if (opts.candidates && opts.candidates > 1) {
|
|
52
|
-
notes.push(
|
|
53
|
-
zh: `并行生成 ${opts.candidates} 个候选(不同 seed),选中一个后再继续加工链`,
|
|
54
|
-
en: `${opts.candidates} candidates in parallel (distinct seeds); pick one before the chain continues`,
|
|
55
|
-
}));
|
|
78
|
+
notes.push(`${opts.candidates} candidates in parallel (distinct seeds); pick one before the chain continues`);
|
|
56
79
|
}
|
|
57
80
|
// Task refs (@last / @name) resolve to an id the human has not seen yet — show it.
|
|
58
81
|
let inputSummary = opts.inputs.join(' ');
|
|
@@ -64,39 +87,54 @@ export function cardFromMakePlan(plan, opts) {
|
|
|
64
87
|
scenario: opts.scenario,
|
|
65
88
|
model: plan.model,
|
|
66
89
|
modelReason: plan.modelReason,
|
|
90
|
+
taskType: plan.generation?.taskType,
|
|
91
|
+
candidates: opts.candidates,
|
|
67
92
|
genParams,
|
|
68
|
-
chain
|
|
69
|
-
deliverables
|
|
70
|
-
notes,
|
|
93
|
+
chain,
|
|
94
|
+
deliverables,
|
|
95
|
+
notes: [...new Set(notes)],
|
|
71
96
|
steps,
|
|
72
97
|
};
|
|
73
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
|
+
}
|
|
74
106
|
/** Cost-impact annotations derived from the parameter set. */
|
|
75
107
|
export function costNotes(card) {
|
|
76
|
-
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' : ''})`];
|
|
77
113
|
const params = card.genParams;
|
|
78
114
|
if (params.texture === false) {
|
|
79
|
-
notes.push(
|
|
115
|
+
notes.push('bare geometry: no texture credits');
|
|
80
116
|
}
|
|
81
117
|
if (params.texture_quality === 'detailed' || params.texture_quality === 'extreme') {
|
|
82
|
-
notes.push(
|
|
118
|
+
notes.push(`texture_quality ${params.texture_quality}: higher price tier`);
|
|
83
119
|
}
|
|
84
120
|
if (params.geometry_quality === 'detailed') {
|
|
85
|
-
notes.push(
|
|
121
|
+
notes.push('geometry detailed (Ultra): extra HD credit');
|
|
86
122
|
}
|
|
87
123
|
if (params.quad === true) {
|
|
88
|
-
notes.push(
|
|
124
|
+
notes.push(card.model && isP2(card.model)
|
|
125
|
+
? 'quad topology: FBX output'
|
|
126
|
+
: 'quad topology: higher price tier, FBX output');
|
|
89
127
|
}
|
|
90
128
|
if (params.generate_parts === true) {
|
|
91
|
-
notes.push(
|
|
129
|
+
notes.push('part generation: higher price tier, bare geometry');
|
|
92
130
|
}
|
|
93
131
|
for (const step of card.chain) {
|
|
94
132
|
if (step.step === 'convert' && isComplexConvert(step.args)) {
|
|
95
|
-
notes.push(
|
|
133
|
+
notes.push('non-default convert options bill as complex convert');
|
|
96
134
|
}
|
|
97
135
|
if (step.step === 'retarget') {
|
|
98
136
|
const count = Array.isArray(step.args.animations) ? step.args.animations.length : 1;
|
|
99
|
-
notes.push(
|
|
137
|
+
notes.push(`retarget ${count} animation(s): billed per animation`);
|
|
100
138
|
}
|
|
101
139
|
}
|
|
102
140
|
return notes;
|
|
@@ -105,18 +143,18 @@ export function costNotes(card) {
|
|
|
105
143
|
export function renderPlanCard(card) {
|
|
106
144
|
const scenario = card.scenario ? getScenario(card.scenario) : undefined;
|
|
107
145
|
const lines = [];
|
|
108
|
-
const title =
|
|
146
|
+
const title = 'Plan';
|
|
109
147
|
lines.push(pc.bold(`── ${title} ──────────────────────────`));
|
|
110
|
-
lines.push(`${
|
|
148
|
+
lines.push(`${'input'}: ${card.inputSummary}`);
|
|
111
149
|
if (scenario)
|
|
112
|
-
lines.push(`${
|
|
150
|
+
lines.push(`${'scenario'}: ${scenario.id} (${scenario.label})`);
|
|
113
151
|
if (card.model) {
|
|
114
|
-
lines.push(`${
|
|
152
|
+
lines.push(`${'model'}: ${card.model}${card.modelReason ? pc.dim(` — ${card.modelReason}`) : ''}`);
|
|
115
153
|
}
|
|
116
154
|
const fmtArgs = (args) => Object.entries(args).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(' ');
|
|
117
155
|
if (card.steps && card.steps.length > 0) {
|
|
118
156
|
// Numbered step list: what each call does, which endpoint, with which args.
|
|
119
|
-
lines.push(`${
|
|
157
|
+
lines.push(`${'steps'}:`);
|
|
120
158
|
card.steps.forEach((step, i) => {
|
|
121
159
|
lines.push(` ${i + 1}. ${step.name} ${pc.dim(`POST ${step.endpoint}`)}${step.describe ? ` — ${step.describe}` : ''}`);
|
|
122
160
|
if (Object.keys(step.args).length > 0)
|
|
@@ -126,28 +164,28 @@ export function renderPlanCard(card) {
|
|
|
126
164
|
else {
|
|
127
165
|
const params = Object.entries(card.genParams);
|
|
128
166
|
if (params.length > 0) {
|
|
129
|
-
lines.push(`${
|
|
167
|
+
lines.push(`${'params'}: ${fmtArgs(card.genParams)}`);
|
|
130
168
|
}
|
|
131
169
|
if (card.chain.length > 0) {
|
|
132
170
|
const chainStr = card.chain
|
|
133
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))
|
|
134
172
|
.join(' → ');
|
|
135
|
-
lines.push(`${
|
|
173
|
+
lines.push(`${'chain'}: ${chainStr}`);
|
|
136
174
|
}
|
|
137
175
|
}
|
|
138
176
|
if (card.deliverables.length > 0) {
|
|
139
|
-
lines.push(`${
|
|
177
|
+
lines.push(`${'deliverables'}: ${card.deliverables.join(', ')}`);
|
|
140
178
|
}
|
|
141
179
|
const cost = costNotes(card);
|
|
142
180
|
if (cost.length > 0) {
|
|
143
|
-
lines.push(`${
|
|
181
|
+
lines.push(`${'cost impact'}:`);
|
|
144
182
|
for (const note of cost)
|
|
145
183
|
lines.push(` · ${note}`);
|
|
146
184
|
}
|
|
147
185
|
for (const note of card.notes)
|
|
148
186
|
lines.push(pc.dim(` ${note}`));
|
|
149
187
|
if (card.expertNotes && card.expertNotes.length > 0) {
|
|
150
|
-
lines.push(`${
|
|
188
|
+
lines.push(`${'expert notes'}:`);
|
|
151
189
|
for (const note of card.expertNotes)
|
|
152
190
|
lines.push(` · ${note}`);
|
|
153
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
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
|
}
|
|
@@ -82,11 +78,11 @@ export function adjustChain(scenarioId, engine, animations) {
|
|
|
82
78
|
const rigArgs = { model: CAPABILITY_DEFAULTS.rig, out_format: 'fbx' };
|
|
83
79
|
if (engine === 'unity') {
|
|
84
80
|
rigArgs.spec = 'unity';
|
|
85
|
-
notes.push(
|
|
81
|
+
notes.push('Unity Humanoid → spec=unity');
|
|
86
82
|
}
|
|
87
83
|
else if (engine === 'unreal') {
|
|
88
84
|
rigArgs.spec = 'unreal';
|
|
89
|
-
notes.push(
|
|
85
|
+
notes.push('Unreal Mannequin → spec=unreal');
|
|
90
86
|
}
|
|
91
87
|
chain = [
|
|
92
88
|
...chain.filter((s) => s.step !== 'convert'),
|
|
@@ -94,7 +90,7 @@ export function adjustChain(scenarioId, engine, animations) {
|
|
|
94
90
|
{ step: 'rig', args: rigArgs },
|
|
95
91
|
{ step: 'retarget', args: { animations: ['preset:idle', 'preset:walk'], out_format: 'fbx' } },
|
|
96
92
|
];
|
|
97
|
-
notes.push(
|
|
93
|
+
notes.push('animation chain outputs FBX (skeleton + animations)');
|
|
98
94
|
}
|
|
99
95
|
if (engine && !animations) {
|
|
100
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
|
}
|
package/dist/commands/ai.js
CHANGED
|
@@ -9,10 +9,10 @@ import * as p from '@clack/prompts';
|
|
|
9
9
|
import { resolveReference } from '../config/history.js';
|
|
10
10
|
import { CliError } from '../core/errors.js';
|
|
11
11
|
import { isInteractive, isYes, log, warn } from '../core/output.js';
|
|
12
|
-
import { t } from '../i18n.js';
|
|
13
12
|
import { EXIT } from '../knowledge/error-catalog.js';
|
|
13
|
+
import { getScenario } from '../knowledge/scenarios.js';
|
|
14
14
|
import { executeMake, planMake } from '../pipeline/make.js';
|
|
15
|
-
import { renderPlanCard } from '../ai/plan-card.js';
|
|
15
|
+
import { cardFromMakePlan, renderPlanCard } from '../ai/plan-card.js';
|
|
16
16
|
import { resolveLlmConfig } from '../ai/llm.js';
|
|
17
17
|
import { buildExpertPlan } from '../ai/experts.js';
|
|
18
18
|
import { runWizard } from '../ai/wizard.js';
|
|
@@ -22,7 +22,7 @@ export function registerAiCommand(program) {
|
|
|
22
22
|
const cmd = program
|
|
23
23
|
.command('ai')
|
|
24
24
|
.description('describe what you want in natural language; experts plan it, you confirm, it runs')
|
|
25
|
-
.argument('[request...]', 'e.g. "
|
|
25
|
+
.argument('[request...]', 'e.g. "a low-poly knight for my mobile game, with a run animation"')
|
|
26
26
|
.option('--wizard', 'force wizard mode even when an LLM is configured');
|
|
27
27
|
withTaskOptions(cmd);
|
|
28
28
|
cmd.action(async (request, flags) => {
|
|
@@ -31,14 +31,15 @@ export function registerAiCommand(program) {
|
|
|
31
31
|
let card;
|
|
32
32
|
let makeOptions;
|
|
33
33
|
if (llm && !flags.wizard && text) {
|
|
34
|
-
log(
|
|
34
|
+
log(`conversation mode (${llm.model})…`);
|
|
35
35
|
try {
|
|
36
|
-
const plan = await buildExpertPlan(text, isInteractive() ? askUser : undefined);
|
|
36
|
+
const plan = await buildExpertPlan(text, isInteractive() ? askUser : undefined, flags.param);
|
|
37
37
|
card = plan.card;
|
|
38
38
|
makeOptions = {
|
|
39
39
|
inputs: [plan.prompt],
|
|
40
40
|
scenario: plan.scenarioId,
|
|
41
41
|
chain: plan.chain,
|
|
42
|
+
model: plan.card.model,
|
|
42
43
|
params: plan.params,
|
|
43
44
|
name: flags.name,
|
|
44
45
|
outDir: flags.out,
|
|
@@ -47,10 +48,7 @@ export function registerAiCommand(program) {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
catch (err) {
|
|
50
|
-
warn(
|
|
51
|
-
zh: `LLM 编排失败(${err instanceof Error ? err.message : err}),回落到向导模式`,
|
|
52
|
-
en: `LLM planning failed (${err instanceof Error ? err.message : err}); falling back to the wizard`,
|
|
53
|
-
}));
|
|
51
|
+
warn(`LLM planning failed (${err instanceof Error ? err.message : err}); falling back to the wizard`);
|
|
54
52
|
const wizard = await runWizard(text);
|
|
55
53
|
card = wizard.card;
|
|
56
54
|
makeOptions = wizardToOptions(wizard.options, flags);
|
|
@@ -59,24 +57,28 @@ export function registerAiCommand(program) {
|
|
|
59
57
|
else {
|
|
60
58
|
// The wizard is interactive by nature — never enter it headless.
|
|
61
59
|
if (!isInteractive()) {
|
|
62
|
-
throw new CliError(
|
|
63
|
-
zh: '向导模式需要交互终端;无头环境请用 tripo make --for <场景>(或配置 llm.base_url/llm.model 走对话模式)',
|
|
64
|
-
en: 'wizard mode needs an interactive terminal; headless callers should use "tripo make --for <scenario>" (or configure llm.base_url/llm.model for conversation mode)',
|
|
65
|
-
}), EXIT.USAGE);
|
|
60
|
+
throw new CliError('wizard mode needs an interactive terminal; headless callers should use "tripo make --for <scenario>" (or configure llm.base_url/llm.model for conversation mode)', EXIT.USAGE);
|
|
66
61
|
}
|
|
67
62
|
const wizard = await runWizard(text || undefined);
|
|
68
63
|
card = wizard.card;
|
|
69
64
|
makeOptions = wizardToOptions(wizard.options, flags);
|
|
70
65
|
}
|
|
66
|
+
// An explicit chain counts as a --then override in planMake, which drops the preset's dual delivery.
|
|
67
|
+
const preset = getScenario(makeOptions.scenario ?? '');
|
|
68
|
+
if (preset && JSON.stringify(makeOptions.chain) === JSON.stringify(preset.chain))
|
|
69
|
+
makeOptions.chain = undefined;
|
|
70
|
+
const client = newClient();
|
|
71
|
+
const preview = await planMake(client, { ...makeOptions, dryRun: true });
|
|
72
|
+
card = { ...card, ...cardFromMakePlan(preview, makeOptions), notes: card.notes };
|
|
73
|
+
makeOptions.model = preview.model;
|
|
71
74
|
process.stderr.write(renderPlanCard(card) + '\n');
|
|
72
75
|
if (!isYes()) {
|
|
73
|
-
const go = await p.confirm({ message:
|
|
76
|
+
const go = await p.confirm({ message: 'Run this plan?' });
|
|
74
77
|
if (p.isCancel(go) || !go) {
|
|
75
|
-
log(
|
|
78
|
+
log('cancelled; no credits spent');
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
|
-
const client = newClient();
|
|
80
82
|
const plan = await planMake(client, makeOptions);
|
|
81
83
|
const outcome = await executeMake(client, plan, makeOptions);
|
|
82
84
|
emitMakeOutcome({ results: [outcome.primary], chainResults: outcome.chainResults, stoppedEarly: outcome.stoppedEarly });
|