tripo-cli 0.7.3 → 0.8.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +10 -7
  3. package/dist/ai/experts.js +25 -19
  4. package/dist/ai/plan-card.js +71 -33
  5. package/dist/ai/wizard.js +13 -17
  6. package/dist/commands/account.js +2 -6
  7. package/dist/commands/ai.js +18 -16
  8. package/dist/commands/batch.js +58 -18
  9. package/dist/commands/completion.js +1 -1
  10. package/dist/commands/config-cmd.js +1 -1
  11. package/dist/commands/doctor.js +1 -3
  12. package/dist/commands/generate.js +35 -4
  13. package/dist/commands/home.js +12 -13
  14. package/dist/commands/login.js +72 -187
  15. package/dist/commands/make.js +47 -25
  16. package/dist/commands/mcp.js +2 -1
  17. package/dist/commands/models.js +47 -0
  18. package/dist/commands/process-groups.js +3 -4
  19. package/dist/commands/shared.js +9 -14
  20. package/dist/commands/task.js +10 -11
  21. package/dist/commands/topup.js +6 -10
  22. package/dist/commands/update.js +6 -10
  23. package/dist/commands/view.js +3 -4
  24. package/dist/config/config.js +1 -1
  25. package/dist/core/client.js +4 -13
  26. package/dist/core/errors.js +16 -11
  27. package/dist/core/output.js +0 -4
  28. package/dist/core/requests.js +61 -17
  29. package/dist/core/task-service.js +19 -20
  30. package/dist/knowledge/chains.js +12 -12
  31. package/dist/knowledge/error-catalog.js +32 -32
  32. package/dist/knowledge/models.js +119 -20
  33. package/dist/knowledge/params.js +96 -8
  34. package/dist/knowledge/scenarios.js +32 -55
  35. package/dist/pipeline/make.js +36 -28
  36. package/dist/pipeline/runner.js +7 -12
  37. package/dist/pipeline/stdin.js +40 -3
  38. package/dist/program.js +6 -11
  39. package/package.json +3 -2
  40. package/skill/SKILL.md +35 -8
  41. package/skill/commands/account.md +1 -1
  42. package/skill/commands/batch.md +13 -3
  43. package/skill/commands/generate.md +35 -10
  44. package/skill/commands/make.md +39 -13
  45. package/skill/commands/process.md +2 -1
  46. package/skill/commands/task.md +2 -2
  47. package/skill/commands/view.md +1 -1
  48. package/skill/common-errors.md +6 -3
  49. package/skill/examples/animation.md +3 -1
  50. package/skill/examples/ar-web.md +6 -4
  51. package/skill/examples/game-asset.md +17 -6
  52. package/skill/examples/pipes.md +2 -2
  53. package/skill/examples/print.md +7 -3
  54. package/dist/i18n.js +0 -19
@@ -1,22 +1,38 @@
1
1
  /**
2
2
  * The `make` orchestrator — one entry point that turns any input (text /
3
3
  * image / multiview / model file / task ref) into finished artifacts,
4
- * applying the dual-model rule, scenario presets, and the processing chain.
4
+ * applying model routing, scenario presets, and the processing chain.
5
5
  * Shared by `tripo make`, `tripo ai`, `tripo redo`, batch, and the MCP server.
6
6
  */
7
7
  import { basename } from 'node:path';
8
8
  import { UsageError } from '../core/errors.js';
9
9
  import { log, warn } from '../core/output.js';
10
- import { buildImageToModel, buildMultiviewToModel, buildStepRequest, buildTextToModel, } from '../core/requests.js';
10
+ import { buildImageToModel, buildMultiviewToModel, buildStepRequest, buildTextToModel, validateChainRequests, } from '../core/requests.js';
11
11
  import { precheckBalance, resolveInputValue, runTask } from '../core/task-service.js';
12
- import { t } from '../i18n.js';
13
12
  import { validateChain, ChainError } from '../knowledge/chains.js';
14
- import { chooseModel, detectLowPolyIntent } from '../knowledge/models.js';
13
+ import { chooseModel, detectDenseGeometryIntent, detectLowPolyIntent, detectRealtimeIntent } from '../knowledge/models.js';
15
14
  import { getScenario } from '../knowledge/scenarios.js';
16
15
  import { classifyInputs } from '../util/input-detect.js';
17
16
  import { runChain, assertSourceReady } from './runner.js';
18
- /** Build the full plan (no network calls except none): what model, what chain, why. */
17
+ function chooseGenerationModel(options, scenario, genParams, prompt) {
18
+ return chooseModel({
19
+ explicit: options.model,
20
+ preferred: scenario?.model,
21
+ faceLimit: typeof genParams.face_limit === 'number' ? genParams.face_limit : undefined,
22
+ quad: genParams.quad === true,
23
+ lowPoly: prompt ? detectLowPolyIntent(prompt) : false,
24
+ realtime: prompt ? detectRealtimeIntent(prompt) : false,
25
+ denseGeometry: prompt ? detectDenseGeometryIntent(prompt) : false,
26
+ params: genParams,
27
+ });
28
+ }
29
+ /** Validate the entire plan without side effects before resolving uploads. */
19
30
  export async function planMake(client, options) {
31
+ const preview = await prepareMake(client, { ...options, dryRun: true });
32
+ checkPlanChain(preview);
33
+ return options.dryRun ? preview : prepareMake(client, options);
34
+ }
35
+ async function prepareMake(client, options) {
20
36
  const classified = classifyInputs(options.inputs);
21
37
  const scenario = options.scenario ? getScenario(options.scenario) : undefined;
22
38
  if (options.scenario && !scenario) {
@@ -25,25 +41,21 @@ export async function planMake(client, options) {
25
41
  const userParams = options.params ?? {};
26
42
  const genParams = { ...(scenario?.genParams ?? {}), ...userParams };
27
43
  const chain = options.chain && options.chain.length > 0 ? options.chain : (scenario?.chain ?? []);
44
+ // --then replaces the scenario chain, so its dual-delivery promise no longer holds.
45
+ const downloadGeneration = scenario?.downloadGeneration === true && !(options.chain && options.chain.length > 0);
28
46
  const warnings = [];
29
47
  if (options.chain && options.chain.length > 0 && scenario && scenario.chain.length > 0) {
30
- warnings.push(t({
31
- zh: `--then 覆盖了 ${scenario.id} 场景默认加工链`,
32
- en: `--then overrides the default ${scenario.id} processing chain`,
33
- }));
48
+ warnings.push(`--then overrides the default ${scenario.id} processing chain`);
34
49
  }
35
50
  switch (classified.kind) {
36
51
  case 'text': {
37
52
  const prompt = classified.values[0].value;
38
- const choice = chooseModel({
39
- explicit: options.model ?? scenario?.model,
40
- faceLimit: typeof genParams.face_limit === 'number' ? genParams.face_limit : undefined,
41
- lowPoly: detectLowPolyIntent(prompt),
42
- });
53
+ const choice = chooseGenerationModel(options, scenario, genParams, prompt);
43
54
  const generation = buildTextToModel(prompt, choice.model, genParams);
44
55
  return {
45
56
  generation,
46
57
  chain,
58
+ downloadGeneration,
47
59
  model: choice.model,
48
60
  modelReason: choice.reason,
49
61
  name: options.name ?? prompt,
@@ -54,14 +66,12 @@ export async function planMake(client, options) {
54
66
  case 'url-image': {
55
67
  const raw = classified.values[0].value;
56
68
  const { value } = await resolveInputValue(client, raw, { dryRun: options.dryRun });
57
- const choice = chooseModel({
58
- explicit: options.model ?? scenario?.model,
59
- faceLimit: typeof genParams.face_limit === 'number' ? genParams.face_limit : undefined,
60
- });
69
+ const choice = chooseGenerationModel(options, scenario, genParams);
61
70
  const generation = buildImageToModel(value, choice.model, genParams);
62
71
  return {
63
72
  generation,
64
73
  chain,
74
+ downloadGeneration,
65
75
  model: choice.model,
66
76
  modelReason: choice.reason,
67
77
  name: options.name ?? basename(raw),
@@ -70,14 +80,12 @@ export async function planMake(client, options) {
70
80
  }
71
81
  case 'multiview': {
72
82
  const views = await assignViews(client, classified.values.map((v) => v.value), { dryRun: options.dryRun });
73
- const choice = chooseModel({
74
- explicit: options.model ?? scenario?.model,
75
- faceLimit: typeof genParams.face_limit === 'number' ? genParams.face_limit : undefined,
76
- });
83
+ const choice = chooseGenerationModel(options, scenario, genParams);
77
84
  const generation = buildMultiviewToModel(views, choice.model, genParams);
78
85
  return {
79
86
  generation,
80
87
  chain,
88
+ downloadGeneration,
81
89
  model: choice.model,
82
90
  modelReason: choice.reason,
83
91
  name: options.name ?? basename(classified.values[0].value),
@@ -89,10 +97,7 @@ export async function planMake(client, options) {
89
97
  const { value } = await resolveInputValue(client, raw, { dryRun: options.dryRun });
90
98
  const generation = buildStepRequest('import', value, {});
91
99
  if (chain.length === 0) {
92
- warnings.push(t({
93
- zh: '导入模型后没有加工步骤;用 --then 指定(如 --then texture,rig)',
94
- en: 'importing a model with no processing steps; add --then (e.g. --then texture,rig)',
95
- }));
100
+ warnings.push('importing a model with no processing steps; add --then (e.g. --then texture,rig)');
96
101
  }
97
102
  return {
98
103
  generation,
@@ -121,6 +126,7 @@ export async function planMake(client, options) {
121
126
  * Skipped when the upstream type is unknown (raw task id without local history).
122
127
  */
123
128
  export function checkPlanChain(plan) {
129
+ validateChainRequests(plan.chain);
124
130
  if (plan.chain.length === 0)
125
131
  return;
126
132
  const upstream = plan.generation?.taskType ?? plan.sourceType;
@@ -147,7 +153,7 @@ export async function executeMake(client, plan, options) {
147
153
  let primary;
148
154
  if (plan.generation) {
149
155
  if (plan.model && plan.modelReason) {
150
- log(t({ zh: `模型选择:${plan.model}(${plan.modelReason})`, en: `model: ${plan.model} (${plan.modelReason})` }));
156
+ log(`model: ${plan.model} (${plan.modelReason})`);
151
157
  }
152
158
  primary = await runTask({
153
159
  client,
@@ -156,10 +162,11 @@ export async function executeMake(client, plan, options) {
156
162
  name: plan.name,
157
163
  outDir: options.outDir,
158
164
  noWait: options.noWait,
159
- noDownload: options.noDownload || plan.chain.length > 0,
165
+ noDownload: options.noDownload || (plan.chain.length > 0 && !plan.downloadGeneration),
160
166
  timeoutMs: options.timeoutMs,
161
167
  notify: options.notify && plan.chain.length === 0,
162
168
  precheckBalance: false,
169
+ onCreated: options.onTaskCreated,
163
170
  });
164
171
  if (options.noWait)
165
172
  return { primary, chainResults: [] };
@@ -183,6 +190,7 @@ export async function executeMake(client, plan, options) {
183
190
  outDir: options.outDir,
184
191
  notify: false,
185
192
  timeoutMs: options.timeoutMs,
193
+ onTaskCreated: options.onTaskCreated,
186
194
  });
187
195
  // If the chain stopped early (not riggable) and no step downloaded a model,
188
196
  // fetch the source artifacts so the user still gets their model.
@@ -5,14 +5,14 @@
5
5
  */
6
6
  import { UsageError } from '../core/errors.js';
7
7
  import { log, warn } from '../core/output.js';
8
- import { buildStepRequest } from '../core/requests.js';
8
+ import { buildStepRequest, validateChainRequests } from '../core/requests.js';
9
9
  import { runTask } from '../core/task-service.js';
10
- import { t } from '../i18n.js';
11
10
  import { getStep, validateChain } from '../knowledge/chains.js';
12
11
  import { NON_BIPED_PRESETS } from '../knowledge/models.js';
13
12
  export async function runChain(options) {
14
13
  const { client, chain } = options;
15
14
  validateChain(chain, options.sourceType);
15
+ validateChainRequests(chain);
16
16
  const results = [];
17
17
  let currentTaskId = options.sourceTaskId;
18
18
  let rigTypeRecommendation;
@@ -22,7 +22,7 @@ export async function runChain(options) {
22
22
  const args = { ...spec.args };
23
23
  if (spec.step === 'rig' && rigTypeRecommendation && args.rig_type === undefined) {
24
24
  args.rig_type = rigTypeRecommendation;
25
- log(t({ zh: `采用 rig-check 推荐体型:${rigTypeRecommendation}`, en: `using rig-check recommendation: ${rigTypeRecommendation}` }));
25
+ log(`using rig-check recommendation: ${rigTypeRecommendation}`);
26
26
  }
27
27
  if (spec.step === 'rig')
28
28
  rigType = String(args.rig_type ?? 'biped');
@@ -33,7 +33,7 @@ export async function runChain(options) {
33
33
  if (requested.length > 0 && requested.every((a) => /^preset:[^:]+$/.test(a))) {
34
34
  delete args.animation;
35
35
  args.animations = bodyPresets;
36
- log(t({ zh: `体型为 ${rigType},双足预设换成 ${bodyPresets.join(', ')}`, en: `rig type is ${rigType}: biped presets swapped for ${bodyPresets.join(', ')}` }));
36
+ log(`rig type is ${rigType}: biped presets swapped for ${bodyPresets.join(', ')}`);
37
37
  }
38
38
  }
39
39
  // avian/others have no preset animations: the rigged model is the final output.
@@ -52,13 +52,11 @@ export async function runChain(options) {
52
52
  // Intermediate steps skip download; final step downloads.
53
53
  noDownload: i < chain.length - 1 && spec.step !== 'rig-check' && !noPresets,
54
54
  precheckBalance: false, // checked once by the caller
55
+ onCreated: options.onTaskCreated,
55
56
  });
56
57
  results.push(result);
57
58
  if (noPresets) {
58
- const message = t({
59
- zh: `体型 ${rigType} 没有预设动画,已跳过 retarget,产物为绑骨模型`,
60
- en: `rig type ${rigType} has no preset animations; skipped retarget and kept the rigged model`,
61
- });
59
+ const message = `rig type ${rigType} has no preset animations; skipped retarget and kept the rigged model`;
62
60
  warn(message);
63
61
  return { results, stoppedEarly: message };
64
62
  }
@@ -68,10 +66,7 @@ export async function runChain(options) {
68
66
  const riggable = output.riggable;
69
67
  rigTypeRecommendation = typeof output.rig_type === 'string' ? output.rig_type : undefined;
70
68
  if (riggable === false) {
71
- const message = t({
72
- zh: 'rig-check 判定该模型不可绑骨,动画链止损停止(后续步骤未执行,未扣积分)',
73
- en: 'rig-check says this model is not riggable; stopping the chain early (no further credits spent)',
74
- });
69
+ const message = 'rig-check says this model is not riggable; stopping the chain early (no further credits spent)';
75
70
  warn(message);
76
71
  return { results, stoppedEarly: message };
77
72
  }
@@ -2,12 +2,48 @@
2
2
  * NDJSON pipe support: downstream commands read the upstream task result from
3
3
  * stdin, enabling `tripo make cat.png | tripo model texture | tripo anim rig`.
4
4
  */
5
- /** Read piped NDJSON/JSON from stdin; returns undefined on a TTY. */
5
+ import { CliError, UsageError } from '../core/errors.js';
6
+ import { EXIT } from '../knowledge/error-catalog.js';
7
+ /**
8
+ * Read piped NDJSON/JSON from stdin; returns undefined only on a TTY.
9
+ * Piped input that carries an upstream error or no task throws, so a broken
10
+ * pipeline never falls back to some other task and spends credits on it.
11
+ */
6
12
  export async function readPipedTask() {
7
13
  if (process.stdin.isTTY)
8
14
  return undefined;
9
15
  const raw = await readAll(process.stdin);
10
- return parsePipedText(raw);
16
+ const upstream = parsePipedError(raw);
17
+ if (upstream) {
18
+ const code = upstream.exit_code;
19
+ const exitCode = code !== undefined && Number.isInteger(code) && code >= 1 && code <= 9 ? code : EXIT.USAGE;
20
+ throw new CliError(`upstream command failed${code !== undefined ? ` (exit ${code})` : ''}: ${upstream.error}; nothing was submitted`, exitCode);
21
+ }
22
+ const task = parsePipedText(raw);
23
+ if (!task) {
24
+ throw new UsageError('no task on stdin (piped input was empty or carried no task_id); nothing was submitted', 'pass the input explicitly, e.g. "tripo model texture @last" or a task id');
25
+ }
26
+ return task;
27
+ }
28
+ /** The upstream error when the last JSON line is an error object (`{"error","exit_code"}`). */
29
+ export function parsePipedError(raw) {
30
+ const lines = raw.trim().split('\n').map((l) => l.trim()).filter(Boolean);
31
+ for (let i = lines.length - 1; i >= 0; i--) {
32
+ let obj;
33
+ try {
34
+ obj = JSON.parse(lines[i]);
35
+ }
36
+ catch {
37
+ continue;
38
+ }
39
+ if (typeof obj !== 'object' || obj === null)
40
+ continue;
41
+ const { error, exit_code: exitCode } = obj;
42
+ if (typeof error !== 'string')
43
+ return undefined;
44
+ return { error, exit_code: typeof exitCode === 'number' ? exitCode : undefined };
45
+ }
46
+ return undefined;
11
47
  }
12
48
  /** Parse piped text: JSON object, NDJSON (last task line wins), or a bare task id. */
13
49
  export function parsePipedText(raw) {
@@ -18,7 +54,8 @@ export function parsePipedText(raw) {
18
54
  for (let i = lines.length - 1; i >= 0; i--) {
19
55
  try {
20
56
  const obj = JSON.parse(lines[i]);
21
- if (typeof obj.task_id === 'string') {
57
+ // Error JSON may name the failed task; it is never an input.
58
+ if (typeof obj.task_id === 'string' && obj.error === undefined) {
22
59
  return { task_id: obj.task_id, type: typeof obj.type === 'string' ? obj.type : undefined };
23
60
  }
24
61
  }
package/dist/program.js CHANGED
@@ -13,16 +13,15 @@ import { registerGenerateCommands } from './commands/generate.js';
13
13
  import { registerHomeAction } from './commands/home.js';
14
14
  import { registerMakeCommand } from './commands/make.js';
15
15
  import { registerMcpCommand } from './commands/mcp.js';
16
+ import { registerModelsCommand } from './commands/models.js';
16
17
  import { registerProcessCommands } from './commands/process-groups.js';
17
18
  import { registerTaskCommands } from './commands/task.js';
18
19
  import { registerTopupCommand } from './commands/topup.js';
19
20
  import { notifyIfOutdated, registerUpdateCommand } from './commands/update.js';
20
21
  import { registerViewCommands } from './commands/view.js';
21
- import { loadConfig } from './config/config.js';
22
22
  import { RefError } from './config/history.js';
23
- import { ApiError, CliError, UsageError } from './core/errors.js';
23
+ import { ApiError, ArtifactDownloadError, CliError, TaskFailedError, UsageError } from './core/errors.js';
24
24
  import { configureOutput, errorLine, isJsonMode } from './core/output.js';
25
- import { setLocale, t } from './i18n.js';
26
25
  import { EXIT } from './knowledge/error-catalog.js';
27
26
  import { cliVersion } from './version.js';
28
27
  import { beginOperation, finishOperation } from './core/analytics.js';
@@ -30,10 +29,7 @@ export function buildProgram() {
30
29
  const program = new Command();
31
30
  program
32
31
  .name('tripo')
33
- .description(t({
34
- zh: 'Tripo 3D 智能 CLI — 一句话/一张图,直出可进引擎的 3D 资产\n高频: make(生成) ai(专家规划) view(预览) topup(充值)',
35
- en: 'Tripo 3D intelligent CLI — turn a sentence or an image into engine-ready 3D assets\ntop commands: make · ai · view · topup',
36
- }))
32
+ .description('Tripo 3D intelligent CLI — turn a sentence or an image into engine-ready 3D assets\ntop commands: make · ai · view · topup')
37
33
  .version(cliVersion(), '-v, --version')
38
34
  .option('--json', 'machine-readable output on stdout (auto-enabled when piped)')
39
35
  .option('--yes', 'skip confirmations (auto-enabled when headless)')
@@ -54,13 +50,10 @@ export function buildProgram() {
54
50
  const profile = resolveGlobalString(actionCommand, 'profile');
55
51
  if (profile)
56
52
  process.env.TRIPO_PROFILE = profile;
57
- const lang = loadConfig().language;
58
- if (lang)
59
- setLocale(lang);
60
53
  const path = [];
61
54
  for (let command = actionCommand; command && command !== program; command = command.parent)
62
55
  path.unshift(command.name());
63
- const excluded = ['mcp', 'docs', 'completion', 'config', 'update', 'use', 'logout', 'home'];
56
+ const excluded = ['mcp', 'docs', 'completion', 'config', 'update', 'use', 'logout', 'home', 'models'];
64
57
  if (actionCommand.name() !== 'mcp')
65
58
  beginOperation(path.join(':'), path.length === 0 || excluded.includes(path[0]) || actionCommand.opts().dryRun === true, undefined, actionCommand.opts().region === 'cn' || actionCommand.opts().region === 'ov' ? actionCommand.opts().region : undefined);
66
59
  // Startup version hint (cached, non-blocking). `update` reports on its
@@ -75,6 +68,7 @@ export function buildProgram() {
75
68
  registerAuthCommands(program);
76
69
  registerTopupCommand(program);
77
70
  registerAccountCommands(program);
71
+ registerModelsCommand(program);
78
72
  registerGenerateCommands(program);
79
73
  registerProcessCommands(program);
80
74
  registerTaskCommands(program);
@@ -158,6 +152,7 @@ export function handleError(err) {
158
152
  ...(err instanceof ApiError
159
153
  ? { api_code: err.apiCode, request_id: err.requestId, http_status: err.httpStatus }
160
154
  : {}),
155
+ ...(err instanceof TaskFailedError || err instanceof ArtifactDownloadError ? { task_id: err.taskId } : {}),
161
156
  ...(err.suggestion ? { suggestion: err.suggestion } : {}),
162
157
  }) + '\n');
163
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tripo-cli",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "Tripo 3D intelligent CLI — turn a prompt or an image into engine-ready 3D assets from your terminal (Tripo V3 API)",
5
5
  "keywords": [
6
6
  "tripo",
@@ -20,6 +20,7 @@
20
20
  "files": [
21
21
  "dist",
22
22
  "skill",
23
+ "CHANGELOG.md",
23
24
  "README.md"
24
25
  ],
25
26
  "engines": {
@@ -48,7 +49,7 @@
48
49
  "@clack/prompts": "^1.7.0",
49
50
  "commander": "^15.0.0",
50
51
  "picocolors": "^1.1.1",
51
- "undici": "^6.28.0",
52
+ "undici": "^6.29.1",
52
53
  "yaml": "^2.9.0"
53
54
  },
54
55
  "devDependencies": {
package/skill/SKILL.md CHANGED
@@ -1,5 +1,19 @@
1
+ ---
2
+ name: tripo-cli
3
+ description: Generate and process 3D models with the Tripo API through the tripo CLI - text or images to GLB/FBX/OBJ/STL/USDZ, texturing, rigging, animation, mesh tools and format conversion, with plan cards and credit estimates. Use when the user wants to create, convert, rig, animate or 3D-print a 3D asset, or mentions Tripo.
4
+ ---
5
+
1
6
  # Tripo CLI Skill
2
7
 
8
+ **Bundled with tripo-cli 0.8.1.**
9
+
10
+ - Display aliases `H3.1`, `H3.0`, `H2.5`, `P1.0`, `P2.0` (case-insensitive) normalize before validation; unknown 3D/image model IDs fail locally.
11
+ - `make` validates deterministic later steps before uploading or generating.
12
+ - Plan cards and `--dry-run` JSON (`generation_credits_estimate`) show the official list-price estimate for the generation step; P1, legacy models and `--then` chain steps are **not estimated**, which never means zero credits.
13
+ - Do not interpret `valid=true` as a quotation or proof that arbitrary passthrough fields are supported. Check warnings and the normalized payload: unsupported P-series fields are removed with warnings, not sent to the API.
14
+ - Image model `chat_image_1` is retired and rejected; `chat_image_1.5` is still valid (announced retirement 2026-12-01).
15
+ - Never automatically swap models and resubmit a paid task.
16
+
3
17
  You (an AI coding agent) can generate production-ready 3D assets with the `tripo` CLI.
4
18
  One command turns a text prompt or an image into local files: a 3D model (glb/fbx/stl/...), a `preview.png` render, and a `task.json` record.
5
19
 
@@ -49,13 +63,23 @@ Regions (ov = international, cn = China mainland) share one CLI: `tripo login --
49
63
  ## Behavior rules (read first)
50
64
 
51
65
  1. **`tripo make` is synchronous and blocking.** It prints a plan card on stderr (one numbered line per API call: endpoint, purpose, args), then submits, polls, downloads artifacts, and exits. In a TTY it asks "Run this plan?" first; `--yes` (auto-enabled when headless) skips the prompt but still prints the card. Wait for the process to finish and read the final JSON from stdout. Do NOT re-implement polling, do NOT add your own timeout shorter than 15 minutes, do NOT stop just because you saw a task_id in the logs.
52
- 2. **stdout is the contract; stderr is commentary.** With `--json` (auto-enabled when piped), stdout carries exactly one final JSON line. Progress/logs go to stderr.
53
- 3. **Artifacts are local files.** The result JSON has `output_dir`, `model_file`, `preview`. Look at `preview.png` to judge quality; re-roll with `tripo redo` if needed.
66
+ 2. **stdout is the contract; stderr is commentary.** With `--json` (auto-enabled when piped), every result command prints exactly one JSON line on stdout (`docs`/`completion` print text, `mcp` speaks JSON-RPC). Progress/logs go to stderr.
67
+ 3. **Artifacts are local files.** The result JSON has `output_dir`, `model_file`, `preview`. Look at `preview.png` to judge quality. Ask the human before re-rolling: `tripo redo` submits a new paid task at once, with no plan card and no confirmation.
54
68
  4. **Exit codes are stable** — branch on them:
55
- - 0 success · 2 usage/params · 3 auth · 4 insufficient credits (tell the human to run `tripo topup`) · 5 content policy · 6 task failed (credits auto-refunded; on `timeout` it is still running — `tripo task watch <id>`, never redo) · 7 network (a failed submit is never resent and may already exist — check `tripo balance` before re-running) · 8 not found · 9 rate limit (retry with backoff)
69
+ - 0 success · 2 usage/params · 3 auth · 4 insufficient credits (tell the human to run `tripo topup`) · 5 content policy
70
+ - 6 task failed (credits auto-refunded; on `timeout` it is still running — `tripo task watch <id>`, never redo)
71
+ - 7 network: a failed submit is never resent and may already exist — check `tripo balance` before re-running. If the task succeeded but its download failed, the error JSON carries `task_id` — run `tripo task get <task_id> --download`, never re-make.
72
+ - 8 not found · 9 rate limit (retry with backoff)
56
73
  5. **Never invent parameters.** Unknown `--param key=value` pairs pass through to the API; stick to documented ones. The full parameter/model/preset reference from the official API docs is bundled: `tripo docs --topic commands/generate` (3D + image generation) and `tripo docs --topic commands/process` (texture/convert/rig/retarget/mesh) — read those instead of browsing the website.
57
74
  6. **Validate before spending: `--dry-run --json`.** Same command plus `--dry-run` prints the plan as JSON with zero network calls and no credits: the chosen `model`, the completed `steps[].payload` for every API call, `warnings` (auto-adjustments) and `errors` (blocking; exit 2). When `valid` is true, re-run without `--dry-run`. Details: `tripo docs --topic commands/make` § "Dry run".
58
- 7. **Don't pick legacy model versions.** The CLI auto-selects `tripo-v3.1` (high fidelity) or `tripo-p1` (low-poly, face budget ≤ 20000). Only override `--model` when the human asks (e.g. `--model tripo-p2` for the P-series **Preview** with quad support, or `tripo-v3.0` / `tripo-v2.5` for reproducing old projects). `tripo-*` names are CLI aliases — the CLI sends the dated wire value (`tripo-p2` → `P2-20260801`); the API rejects the alias itself with 1004. Alias ↔ wire ↔ status table: `tripo docs --topic commands/generate` ("Models & Versions").
75
+ 7. **Route models by use, topology, and geometry.**
76
+ - Explicit `--model` always wins.
77
+ - `tripo-v3.1` (H3.1) is the official default flagship model; the CLI also requires it for dense/high-poly geometry, `smart_low_poly=true`, `generate_parts=true`, any explicit `geometry_quality`, or a face budget outside P2's range (below 48, above 50000, or above 25000 with quad).
78
+ - Next, a `--for` preset outranks prompt hints: `film` and `print` use H3.1; `game-mobile`, `game-pc`, `ar-web`, `anim` and `toy` use P2.
79
+ - Without a preset, the CLI selects `tripo-p2` for real-time/game/editable assets, low-poly or voxel requests, quad topology, and a face budget within P2's range; anything else stays on H3.1.
80
+ - P2 costs 100–130 credits per generation versus 10–50 for H3.1 before add-ons, so before running P2, relay the plan card's estimate to the human and get confirmation.
81
+ - `tripo-p1` is an explicit lower-cost option. Only use legacy `tripo-v3.0` / `tripo-v2.5` to reproduce old projects.
82
+ - `tripo-*` names are CLI aliases — the CLI sends the dated wire value (`tripo-p2` → `P2-20260801`); the API rejects the alias itself with 1004. Alias ↔ wire ↔ status table: `tripo docs --topic commands/generate` ("Models & Versions").
59
83
 
60
84
  ## The one command you usually need
61
85
 
@@ -67,10 +91,10 @@ tripo make hero.glb --then texture,rig --json --yes # import + process
67
91
  tripo make @last --then convert:fbx --json --yes # continue from last task
68
92
  ```
69
93
 
70
- - `--for` scenario presets: `game-mobile` `game-pc` `film` `print` `ar-web` `anim` `toy`
94
+ - `--for` scenario presets: `film` `print` → H3.1; `game-mobile` `game-pc` `ar-web` `anim` `toy` → P2 (the preset outranks prompt hints; `--model` and H-only requirements still win)
71
95
  - `--then` chain steps: `texture convert import rig-check rig retarget segment complete decimate uv smartsegment`
72
96
  - step args: `convert:format=FBX,texture_size=2048` (bare value = primary arg: `convert:fbx`, `decimate:5000`)
73
- - Output JSON: `{"task_id","type","status","credits_consumed","output_dir","files",["model_file"],["preview"],["normalize_scale"],["chain"],["credits_breakdown"]}` — with `--then`, `credits_consumed` is the **total** for the run and `credits_breakdown` itemizes every task (`[{task_id,type,credits_consumed}]`, generation first)
97
+ - Output JSON: `{"task_id","type","status","credits_consumed","output_dir","files",["model_file"],["source_model_file"],["preview"],["normalize_scale"],["chain"],["credits_breakdown"]}` — with `--then`, `credits_consumed` is the **total** for the run and `credits_breakdown` itemizes every task (`[{task_id,type,credits_consumed}]`, generation first)
74
98
  - `normalize_scale` appears when the input was a model file / URL: the import rescaled it (final size = original size × normalize_scale; `1` = untouched) and nothing scales it back — divide by it to recover the original size
75
99
 
76
100
  ## Pipes (composing steps yourself)
@@ -81,23 +105,26 @@ Downstream commands read the upstream task from stdin:
81
105
  tripo make cat.png --json | tripo anim rig --json | tripo anim retarget --param animation=preset:walk --json
82
106
  ```
83
107
 
108
+ If an upstream command fails or prints no task, the downstream submits nothing and exits with the upstream's exit code (or 2). Without a terminal on stdin, process commands never fall back to `@last`; pass the input explicitly.
109
+
84
110
  ## Other commands
85
111
 
86
112
  | command | purpose |
87
113
  | --- | --- |
88
114
  | `tripo task get/watch <id> --download` | inspect / block on / download an existing task |
89
115
  | `tripo balance` / `tripo usage` | credits: `{"balance","frozen"}` / recent spend |
116
+ | `tripo models` | accepted model ids per capability, defaults and aliases (local, no API call) |
90
117
  | `tripo redo [@last]` | same request, new seed |
91
118
  | `tripo view [@last]` | (humans) open a local 3D preview; agents read `preview.png` instead |
92
119
  | `tripo files upload <path>` | get a `file_token` |
93
- | `tripo batch run manifest.yaml` | bulk jobs, resumable |
120
+ | `tripo batch run manifest.yaml` | bulk jobs, resumable; no plan card, so estimate and confirm first (`commands/batch` § Cost) |
94
121
  | `tripo mcp` | run as an MCP server (tools: tripo_make, tripo_task_get, tripo_task_wait, tripo_balance, tripo_history) |
95
122
  | `tripo docs --topic commands/<name>` | print detailed docs for any command |
96
123
 
97
124
  ## Detailed docs in this package
98
125
 
99
126
  - `commands/` — one file per command with every flag and parameter
100
- - model versions · H series (`tripo-v3.x`) · P series (`tripo-p1`, `tripo-p2` Preview) · alias → wire value · GA / Preview / Legacy status · quad → `commands/generate` § "Models & Versions" (also the P Series endpoint entry points)
127
+ - model versions · H series (`tripo-v3.x`) · P series (`tripo-p1`, `tripo-p2`) · alias → wire value · GA / Legacy status · quad → `commands/generate` § "Models & Versions" (also the P Series endpoint entry points)
101
128
  - `examples/` — copy-paste recipes per scenario (game/print/animation/...)
102
129
  - `common-errors.md` — error table with fixes
103
130
 
@@ -54,7 +54,7 @@ tripo config context default_scenario=game-mobile output_dir=./assets # per-di
54
54
  ```
55
55
 
56
56
  Env overrides: `TRIPO_API_KEY` `TRIPO_PROFILE` `TRIPO_REGION` `TRIPO_API_BASE_URL` `TRIPO_LLM_BASE_URL` `TRIPO_LLM_API_KEY` `TRIPO_LLM_MODEL`.
57
- `region`/`api_base_url`/`platform_base_url` are stored per profile; `language`/`llm.*`/`default_output_dir` are shared.
57
+ `region`/`api_base_url`/`platform_base_url` are stored per profile; `llm.*`/`default_output_dir` are shared.
58
58
 
59
59
  ## tripo doctor
60
60
 
@@ -26,8 +26,18 @@ jobs:
26
26
  params: { texture_quality: detailed }
27
27
  ```
28
28
 
29
+ ## Cost
30
+
31
+ - Every job is a full `make` that runs without a plan card or confirmation.
32
+ - The five P2 presets (`game-mobile` `game-pc` `ar-web` `anim` `toy`) cost 100–130 credits per job for the generation step alone; the example's default `game-mobile` is at least 110 per job, before chain steps.
33
+ - Before running, dry-run one job per preset/params combination with the matching `make` flags (`tripo make "a bronze sword" --for game-mobile --then texture,convert:fbx --dry-run --json`), multiply `generation_credits_estimate` by the number of jobs that share it (`null` means not estimated, never zero), relay the total to the human and get confirmation.
34
+
29
35
  ## Behavior
30
36
 
31
- - State is saved next to the manifest (`assets.yaml.state.json`) after every job. Re-running the same command **skips succeeded jobs** (resume). `--fresh` starts over.
32
- - Failed jobs retry up to `--retries` times, then are reported; exit code 6 if any job stays failed.
33
- - Final stdout JSON: `{"total","success","failed","state_file","jobs":{...}}`.
37
+ - State is saved next to the manifest (`assets.yaml.state.json`) after every job and as soon as each paid task is created. Re-running the same command **skips succeeded jobs** (resume). `--fresh` starts over.
38
+ - A job is retried (up to `--retries` times) only when the failed attempt left no paid task behind: it failed before anything was submitted, or its only task failed server-side (failed/cancelled tasks are refunded).
39
+ - Anything else stops the job as `needs_attention`, and it is **never resubmitted automatically**, not even on re-run: a create request whose response was lost (the task may exist and be billed), a timeout, a failed artifact download, or a failure after an earlier task of the job succeeded.
40
+ - Its state entry records `tasks` (every task created for the job, in order), `error`, and `suggestion` (the next step, e.g. `tripo task get <id> --download`, or `tripo make <task_id> --then ...` to continue from the last successful task).
41
+ - To resubmit a `needs_attention` job on purpose, delete its entry from the state file, or use `--fresh` to rerun everything.
42
+ - Exit code 6 if any job stays `failed` or `needs_attention`.
43
+ - Final stdout JSON: `{"total","success","failed","needs_attention","state_file","jobs":{...}}`.
@@ -14,12 +14,12 @@ tripo generate multiview-to-model @last # reuse the 4 views of an image
14
14
 
15
15
  ### P Series (`tripo-p1` / `tripo-p2`) — same endpoints, different model
16
16
 
17
- The P series (low-poly, clean topology) uses the **same three endpoints**; only the `model` value changes. The official docs document it as a separate "P Series" tab on each generation page — this is the direct entry point:
17
+ The P series (editable low-poly topology) uses the **same three endpoints**; only the `model` value changes. The official docs document it as a separate "P Series" tab on each generation page — this is the direct entry point:
18
18
 
19
19
  ```bash
20
- tripo generate text-to-model "a low poly knight" --model tripo-p1 -p face_limit=8000 # → model: P1-20260311
21
- tripo generate image-to-model photo.png --model tripo-p2 -p quad=true -p face_limit=12000 # → model: P2-20260801 (Preview)
22
- tripo generate multiview-to-model front.png back.png --model tripo-p1
20
+ tripo generate text-to-model "a low poly knight for a game" -p face_limit=8000 # → model: P2-20260801
21
+ tripo generate image-to-model photo.png --model tripo-p2 -p quad=true -p face_limit=12000 # → model: P2-20260801
22
+ tripo generate multiview-to-model front.png back.png --model tripo-p1 # explicit lower-cost P-series choice
23
23
  ```
24
24
 
25
25
  | endpoint | H series (standard) page | P series page |
@@ -32,17 +32,25 @@ tripo generate multiview-to-model front.png back.png --model tripo-p1
32
32
 
33
33
  ### 3D models (`--model`) — Models & Versions
34
34
 
35
- Keywords: model versions · H series (v3.x) · P series (P1 / P2) · Preview · quad · alias → wire value.
35
+ Keywords: model versions · H series (v3.x) · P series (P1 / P2) · quad · alias → wire value.
36
36
 
37
37
  | alias (what you type) | wire value (what the API receives) | status | positioning | notes |
38
38
  | --- | --- | --- | --- | --- |
39
- | `tripo-v3.1` (default) | `v3.1-20260211` | GA | latest high-precision; AAA / 3D printing | full feature set |
40
- | `tripo-p1` | `P1-20260311` | GA | best low-poly; games / UGC / mobile | face_limit 50–20000; **no** `quad` `smart_low_poly` `generate_parts` `geometry_quality`; ~2s mesh |
41
- | `tripo-p2` | `P2-20260801` | **Preview** (2026-08) | P1 upgrade (P series) | adds `quad` (forces FBX); face_limit 48–50000 tri / 48–25000 quad (validated locally); still **no** `smart_low_poly` `generate_parts` `geometry_quality` (stripped with a warning); credits 100 (no texture) / 110 / 120 / 130 for standard / detailed / extreme. Explicit `--model` only — the CLI never auto-selects it |
39
+ | `tripo-v3.1` | `v3.1-20260211` | GA | official default flagship model; dense geometry, H-only topology features, film / print | full feature set; 10–40 credits (text) or 20–50 (image / multiview) by texture tier, plus `geometry_quality=detailed` +20, `quad` +5, `smart_low_poly` +10, `generate_parts` +20 |
40
+ | `tripo-p1` | `P1-20260311` | GA | explicit lower-cost low-poly choice | face_limit 50–20000; **no** `quad` `smart_low_poly` `generate_parts` `geometry_quality`; no published price, so plans leave it unestimated |
41
+ | `tripo-p2` | `P2-20260801` | GA (2026-08) | automatic choice for real-time, game, editable, low-poly, voxel, quad, or a face budget within P2's range | adds `quad` (forces FBX); face_limit 48–50000 tri / 48–25000 quad (validated locally); still **no** `smart_low_poly` `generate_parts` `geometry_quality` (stripped with a warning). 100–130 credits for every input by texture tier, no add-ons; confirm the plan card's estimate with the human before spending |
42
42
  | `tripo-v3.0` / `tripo-v2.5` | `v3.0-20250812` / `v2.5-20250123` | Legacy | previous generations | only when the human asks |
43
43
 
44
44
  **Alias vs wire value.** `tripo-*`, `p1`, `p2`, `v3.1` … are CLI-side aliases; the CLI always sends the dated wire value in the `model` field (`--model tripo-p2` → `"model": "P2-20260801"`). The v3 server whitelist accepts wire values only — sending an alias straight to the API returns 1004 `invalid model` with the allowed list. `task.json` records the wire value that was actually used. `tripo-turbo` / `tripo-v2.0` appear in some docs but are **not** on the v3 whitelist (400).
45
45
 
46
+ **Automatic routing.**
47
+
48
+ - Explicit `--model` always wins, followed by `-p model=<string>`; both are normalized and checked against the selected model's capabilities.
49
+ - With neither form, dense/high-poly geometry, `smart_low_poly=true`, `generate_parts=true`, any explicit `geometry_quality`, and a face budget outside P2's range (below 48, above 50000, or above 25000 with quad) select H3.1 first.
50
+ - A scenario preset is the next preference and outranks prompt hints: film and print use H3.1, while game-mobile, game-pc, ar-web, anim, and toy use P2.
51
+ - Without a preset, real-time/game/editable/topology hints, low-poly/voxel intent, `quad=true`, or a face budget within P2's range select P2.
52
+ - Everything else uses H3.1, the official default model.
53
+
46
54
  Official docs (ov / cn):
47
55
  - Models & Versions: <https://developers.tripo3d.ai/en/docs/models-and-versions> · <https://developers.tripo3d.com/zh/docs/models-and-versions>
48
56
  - P Series model detail: <https://developers.tripo3d.ai/en/models/p1> · <https://developers.tripo3d.com/zh/models/p1>
@@ -111,14 +119,31 @@ tripo generate image-to-splat photo.png # gaussian splat
111
119
  | `seedream_v4` | ByteDance | GA | text-to-image default (balanced); **not accepted by image-to-image** |
112
120
  | `seedream_v5` | ByteDance | GA | image-to-image default; higher quality |
113
121
  | `banana` / `banana_pro` / `banana2` | Google | GA | fast / higher quality / latest fast |
114
- | `chat_image_1` / `chat_image_1.5` | OpenAI | retiring 2026-10-23 / 2026-12-01 | GPT image; 1.5 = higher quality — move to `chat_image_2` or 2.5 |
122
+ | `chat_image_1` | OpenAI | retired 2026-10-23 | rejected locally; explicitly choose a replacement, never auto-resubmit |
123
+ | `chat_image_1.5` | OpenAI | active; retiring 2026-12-01 | remains supported until retirement |
115
124
  | `chat_image_2` | OpenAI | GA | GPT, best quality |
116
125
  | `chat_image_2.5_flare` / `chat_image_2.5_sunburst` | OpenAI | GA | 2.5 series: flare = speed tier (chat_image_2 quality, much faster); sunburst = refined tier (keeps unedited areas closest to the original) |
117
126
 
118
- Image model ids are sent as-is (no alias normalization). Full list with retirement dates: <https://developers.tripo3d.ai/en/docs/models-and-versions>.
127
+ Image model ids are endpoint-whitelisted and sent as-is (no Gemini/display alias mapping). Unknown IDs fail locally. Since 0.8.0 the CLI rejects retired `chat_image_1` without automatic replacement; `chat_image_1.5` remains valid as of 2026-10-27. Full list with retirement dates: <https://developers.tripo3d.ai/en/docs/models-and-versions>.
119
128
 
120
129
  ### Image parameters
121
130
 
131
+ - Since 0.8.0 the CLI validates `model`, `quality`, `background`, `output_format` and known `size` rules on both image endpoints.
132
+ - Image editing preflights these and reference counts (Seedream ≤4, Banana ≤10, Chat Image ≤16) before uploading any local file.
133
+ - Standalone image/multiview-to-model likewise validates known model/face/quad constraints before uploads; text-to-model validates the effective prompt's 1024-character limit.
134
+ - Non-2.5 `background` is removed with an ignored warning (matching the documented server behavior); 2.5 transparency rejects JPEG. `quality=auto` is rejected.
135
+ - Unspecified values remain unspecified; the CLI does not invent provider fields or silently change models.
136
+
137
+ Size rules (official tabs, checked 2026-10-27):
138
+ - `banana`: fixed `1024x1024`, `832x1248`, `1248x832`, `864x1184`, `1184x864`, `896x1152`, `1152x896`, `768x1344`, `1344x768`, `1536x672`. Omitted size is left to the service; the documented default differs from its explicit fixed-size list.
139
+ - `banana_pro`: `1K` / `2K` / `4K` or positive integer `WxH`; no undocumented pixel limits are invented.
140
+ - `banana2`: `0.5K` / `1K` / `2K` / `4K` only.
141
+ - `seedream_v4`: `2K` / `4K`; `seedream_v5`: `2K` / `3K`; explicit `WxH` requires aspect ratio 1:16–16:1. The official MP ranges are rounded (v4 approximately 3.69–16.78 MP; v5 3.69–10.40 MP); exact pixel-count cutoffs are not asserted by the CLI and still require server validation.
142
+ - `chat_image_1.5`: `1024x1024` / `1024x1536` / `1536x1024` / `auto`.
143
+ - `chat_image_2` / 2.5: `auto` or `WxH`, each edge ≤3840 and divisible by 16, long/short ratio ≤3, total pixels 655360–8294400. `1536x864` and `1088x1920` work; `1080x1920` fails locally.
144
+
145
+ 3D display aliases `H3.1` / `P1.0` / `P2.0` (plus `P2.0 Preview`, the pre-GA name kept for compatibility) and legacy `H3.0` / `H2.5` map to the same dated IDs and validation as `tripo-v3.1` / `tripo-p1` / `tripo-p2` / `tripo-v3.0` / `tripo-v2.5`; every model alias is case-insensitive. All `face_limit` values must be finite integers. Unknown model names are rejected, not forwarded.
146
+
122
147
  | param | default | endpoint | notes |
123
148
  | --- | --- | --- | --- |
124
149
  | `prompt` | — | t2i (required), i2i | ≤1024 chars; i2i: required unless `template` is set |