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.
- package/CHANGELOG.md +33 -0
- package/README.md +10 -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/completion.js +1 -1
- 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/models.js +47 -0
- 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 +6 -11
- package/package.json +3 -2
- package/skill/SKILL.md +35 -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/dist/commands/make.js
CHANGED
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
7
|
import { TripoClient } from '../core/client.js';
|
|
8
|
-
import { AuthError, UsageError } from '../core/errors.js';
|
|
8
|
+
import { ArtifactDownloadError, AuthError, UsageError } from '../core/errors.js';
|
|
9
9
|
import { emitResult, isInteractive, log, warn } from '../core/output.js';
|
|
10
10
|
import { buildStepRequest } from '../core/requests.js';
|
|
11
|
-
import { t } from '../i18n.js';
|
|
12
11
|
import { ChainError, getStep } from '../knowledge/chains.js';
|
|
13
12
|
import { EXIT } from '../knowledge/error-catalog.js';
|
|
14
13
|
import { scenarioIds } from '../knowledge/scenarios.js';
|
|
15
|
-
import { cardFromMakePlan, costNotes, renderPlanCard } from '../ai/plan-card.js';
|
|
14
|
+
import { cardFromMakePlan, costNotes, generationCreditsEstimate, renderPlanCard } from '../ai/plan-card.js';
|
|
16
15
|
import { checkPlanChain, executeMake, planMake } from '../pipeline/make.js';
|
|
17
16
|
import { detectInput, InputDetectError } from '../util/input-detect.js';
|
|
18
17
|
import { newClient, parseChainFlag, printTaskResult, summarizeCredits, withTaskOptions } from './shared.js';
|
|
@@ -50,9 +49,9 @@ async function confirmPlan(plan, inputs, flags) {
|
|
|
50
49
|
log(renderPlanCard(card));
|
|
51
50
|
if (!isInteractive())
|
|
52
51
|
return true;
|
|
53
|
-
const go = await p.confirm({ message:
|
|
52
|
+
const go = await p.confirm({ message: 'Run this plan?' });
|
|
54
53
|
if (p.isCancel(go) || !go) {
|
|
55
|
-
log(
|
|
54
|
+
log('cancelled; no credits spent');
|
|
56
55
|
return false;
|
|
57
56
|
}
|
|
58
57
|
return true;
|
|
@@ -90,20 +89,14 @@ export async function runMake(inputs, flags) {
|
|
|
90
89
|
return { results: [outcome.primary], chainResults: outcome.chainResults, stoppedEarly: outcome.stoppedEarly };
|
|
91
90
|
}
|
|
92
91
|
// -n candidates: N parallel generations with distinct seeds.
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const preview = await planMake(client, { ...options, dryRun: true });
|
|
93
|
+
checkCandidatePlan(preview, n);
|
|
95
94
|
const basePlan = await planMake(client, options);
|
|
96
|
-
if (!basePlan.generation) {
|
|
97
|
-
throw new UsageError('-n only applies to generation inputs (text/image), not task references');
|
|
98
|
-
}
|
|
99
95
|
const chain = basePlan.chain;
|
|
100
96
|
const interactive = isInteractive();
|
|
101
|
-
if (chain.length > 0 && !interactive) {
|
|
102
|
-
throw new UsageError('-n with --then needs an interactive terminal to pick the winner; run the chain afterwards with "tripo make <task-id> --then ..."');
|
|
103
|
-
}
|
|
104
97
|
if (!(await confirmPlan(basePlan, inputs, flags)))
|
|
105
98
|
return cancelled;
|
|
106
|
-
log(
|
|
99
|
+
log(`generating ${n} candidates in parallel…`);
|
|
107
100
|
const seeds = Array.from({ length: n }, () => Math.floor(Math.random() * 2 ** 31));
|
|
108
101
|
const runs = seeds.map((seed, i) => (async () => {
|
|
109
102
|
const candidateOptions = {
|
|
@@ -121,6 +114,9 @@ export async function runMake(inputs, flags) {
|
|
|
121
114
|
for (const s of settled) {
|
|
122
115
|
if (s.status === 'fulfilled')
|
|
123
116
|
results.push(s.value.primary);
|
|
117
|
+
// A finished (billed) candidate stays in the results even if its download failed.
|
|
118
|
+
else if (s.reason instanceof ArtifactDownloadError)
|
|
119
|
+
results.push({ ...s.reason.result, download_error: s.reason.message });
|
|
124
120
|
else
|
|
125
121
|
warn(String(s.reason instanceof Error ? s.reason.message : s.reason));
|
|
126
122
|
}
|
|
@@ -134,7 +130,7 @@ export async function runMake(inputs, flags) {
|
|
|
134
130
|
let stoppedEarly;
|
|
135
131
|
if (chain.length > 0 && interactive) {
|
|
136
132
|
const pick = await p.select({
|
|
137
|
-
message:
|
|
133
|
+
message: 'pick a candidate to continue the chain',
|
|
138
134
|
options: results.map((r, i) => ({
|
|
139
135
|
value: r.task_id,
|
|
140
136
|
label: `#${i + 1} ${r.task_id.slice(0, 8)} ${r.output_dir ?? ''}`,
|
|
@@ -158,6 +154,17 @@ export async function runMake(inputs, flags) {
|
|
|
158
154
|
}
|
|
159
155
|
return { results, chainResults, stoppedEarly };
|
|
160
156
|
}
|
|
157
|
+
/** Shared by normal execution and dry-run; must run before upload. */
|
|
158
|
+
function checkCandidatePlan(plan, count) {
|
|
159
|
+
if (count > 4)
|
|
160
|
+
throw new UsageError('at most 4 candidates per run (-n 4)');
|
|
161
|
+
if (count > 1 && !plan.generation) {
|
|
162
|
+
throw new UsageError('-n only applies to generation inputs (text/image), not task references');
|
|
163
|
+
}
|
|
164
|
+
if (count > 1 && plan.chain.length > 0 && !isInteractive()) {
|
|
165
|
+
throw new UsageError('-n with --then needs an interactive terminal to pick the winner; run the chain afterwards with "tripo make <task-id> --then ..."');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
161
168
|
const UPSTREAM_PLACEHOLDER = '<upstream>';
|
|
162
169
|
/** Dry runs never send a request, so a missing API key must not block them. */
|
|
163
170
|
function dryRunClient() {
|
|
@@ -208,6 +215,7 @@ async function dryRunMake(inputs, flags, options) {
|
|
|
208
215
|
candidates: flags.candidates,
|
|
209
216
|
warnings,
|
|
210
217
|
errors,
|
|
218
|
+
generation_credits_estimate: null,
|
|
211
219
|
cost_notes: [],
|
|
212
220
|
});
|
|
213
221
|
return;
|
|
@@ -224,10 +232,13 @@ async function dryRunMake(inputs, flags, options) {
|
|
|
224
232
|
if (plan.chain.length > 0 && !plan.generation && !plan.sourceType) {
|
|
225
233
|
warnings.push(`chain legality unverified: upstream task type unknown (no local history for ${plan.sourceTaskId}); the real run checks it via the API`);
|
|
226
234
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
235
|
+
try {
|
|
236
|
+
checkCandidatePlan(plan, flags.candidates);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
if (!(err instanceof UsageError))
|
|
240
|
+
throw err;
|
|
241
|
+
errors.push(err.message);
|
|
231
242
|
}
|
|
232
243
|
// Same card the real run shows; chain-step args are replaced by the
|
|
233
244
|
// normalized payloads so the human sees the completed parameters too.
|
|
@@ -281,6 +292,7 @@ async function dryRunMake(inputs, flags, options) {
|
|
|
281
292
|
candidates: flags.candidates,
|
|
282
293
|
warnings,
|
|
283
294
|
errors,
|
|
295
|
+
generation_credits_estimate: generationCreditsEstimate(card) ?? null,
|
|
284
296
|
cost_notes: costNotes(card),
|
|
285
297
|
}, renderPlanCard(card));
|
|
286
298
|
}
|
|
@@ -288,18 +300,18 @@ async function dryRunMake(inputs, flags, options) {
|
|
|
288
300
|
function emitDryRun(result, cardText) {
|
|
289
301
|
const lines = cardText ? [cardText] : [];
|
|
290
302
|
if (result.warnings.length > 0) {
|
|
291
|
-
lines.push(
|
|
303
|
+
lines.push('warnings (auto-adjusted):');
|
|
292
304
|
for (const w of result.warnings)
|
|
293
305
|
lines.push(` ! ${w}`);
|
|
294
306
|
}
|
|
295
307
|
if (result.errors.length > 0) {
|
|
296
|
-
lines.push(
|
|
308
|
+
lines.push('errors (blocking):');
|
|
297
309
|
for (const e of result.errors)
|
|
298
310
|
lines.push(` ✗ ${e}`);
|
|
299
311
|
}
|
|
300
312
|
lines.push(result.valid
|
|
301
|
-
?
|
|
302
|
-
:
|
|
313
|
+
? 'dry run: plan is valid; nothing submitted, no credits spent'
|
|
314
|
+
: 'dry run: plan is invalid; fix the errors above and retry');
|
|
303
315
|
emitResult(result, lines.join('\n'));
|
|
304
316
|
if (!result.valid)
|
|
305
317
|
process.exitCode = EXIT.USAGE;
|
|
@@ -315,6 +327,10 @@ export function emitMakeOutcome(outcome) {
|
|
|
315
327
|
: primary;
|
|
316
328
|
if (outcome.chainResults.length > 0)
|
|
317
329
|
merged.chain = outcome.chainResults;
|
|
330
|
+
if (finalChain && primary.model_file) {
|
|
331
|
+
merged.source_output_dir = primary.output_dir;
|
|
332
|
+
merged.source_model_file = primary.model_file;
|
|
333
|
+
}
|
|
318
334
|
// Chain steps take a task id input, so the API omits normalize_scale on them; the model keeps the import's scale.
|
|
319
335
|
if (primary.normalize_scale !== undefined)
|
|
320
336
|
merged.normalize_scale ??= primary.normalize_scale;
|
|
@@ -329,9 +345,9 @@ export function emitMakeOutcome(outcome) {
|
|
|
329
345
|
printInlineImage(merged.preview);
|
|
330
346
|
}
|
|
331
347
|
else {
|
|
332
|
-
const lines = outcome.results.map((r, i) => `#${i + 1} ${r.task_id} ${r.output_dir ?? r.status}`);
|
|
348
|
+
const lines = outcome.results.map((r, i) => `#${i + 1} ${r.task_id} ${r.output_dir ?? (r.download_error ? 'download failed' : r.status)}`);
|
|
333
349
|
if (credits.breakdown.length > 0) {
|
|
334
|
-
lines.push(
|
|
350
|
+
lines.push(`credits consumed (total): ${credits.total}`);
|
|
335
351
|
for (const line of credits.breakdown)
|
|
336
352
|
lines.push(` ${line.type} ${line.task_id} ${line.credits_consumed}`);
|
|
337
353
|
}
|
|
@@ -342,4 +358,10 @@ export function emitMakeOutcome(outcome) {
|
|
|
342
358
|
credits_breakdown: credits.breakdown,
|
|
343
359
|
}, lines.join('\n'));
|
|
344
360
|
}
|
|
361
|
+
const downloadFailed = outcome.results.filter((r) => r.download_error);
|
|
362
|
+
for (const r of downloadFailed) {
|
|
363
|
+
warn(`${r.task_id} succeeded but its download failed; do not regenerate, run "tripo task get ${r.task_id} --download"`);
|
|
364
|
+
}
|
|
365
|
+
if (downloadFailed.length > 0)
|
|
366
|
+
process.exitCode = EXIT.NETWORK;
|
|
345
367
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -29,7 +29,7 @@ const TOOLS = [
|
|
|
29
29
|
then: { type: 'string', description: 'processing chain, e.g. "texture,rig,convert:fbx"' },
|
|
30
30
|
model: {
|
|
31
31
|
type: 'string',
|
|
32
|
-
description: 'force model: tripo-v3.1 / v3.1-20260211
|
|
32
|
+
description: 'force model: tripo-v3.1 / v3.1-20260211, tripo-p1 / P1-20260311, or tripo-p2 / P2-20260801. Automatic routing prefers P2 for real-time/editable topology and uses H3.1 for dense or H-only geometry; aliases and dated versions are both accepted',
|
|
33
33
|
},
|
|
34
34
|
output_dir: { type: 'string', description: 'directory for artifacts' },
|
|
35
35
|
},
|
|
@@ -168,6 +168,7 @@ async function callTool(params) {
|
|
|
168
168
|
status: final.status,
|
|
169
169
|
output_dir: final.output_dir ?? outcome.primary.output_dir,
|
|
170
170
|
model_file: final.model_file ?? outcome.primary.model_file,
|
|
171
|
+
source_model_file: final === outcome.primary ? undefined : outcome.primary.model_file,
|
|
171
172
|
preview: final.preview ?? outcome.primary.preview,
|
|
172
173
|
normalize_scale: final.normalize_scale ?? outcome.primary.normalize_scale,
|
|
173
174
|
chain: outcome.chainResults.map((r) => ({ task_id: r.task_id, type: r.type })),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** `tripo models` — list the model ids each capability accepts (local whitelist, no API call). */
|
|
2
|
+
import { emitResult } from '../core/output.js';
|
|
3
|
+
import { CAPABILITY_DEFAULTS, DECIMATE_MODELS, IMAGE_TO_IMAGE_MODELS, LEGACY_3D_MODELS, MODEL_ALIASES, MODEL_P1, MODEL_P2, MODEL_V31, RIG_MODEL_ALIASES, RIG_MODEL_V1, RIG_MODEL_V25, RIG_MODEL_V30, SEGMENT_MODELS, TEXTURE_MODELS, TEXT_TO_IMAGE_MODELS, } from '../knowledge/models.js';
|
|
4
|
+
/** Wire ids with the CLI aliases that normalize to each of them. */
|
|
5
|
+
function withAliases(ids, aliases) {
|
|
6
|
+
return ids.map((id) => {
|
|
7
|
+
const names = Object.keys(aliases).filter((alias) => aliases[alias] === id && alias !== id);
|
|
8
|
+
return names.length ? { id, aliases: names } : { id };
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function plain(ids) {
|
|
12
|
+
return ids.map((id) => ({ id }));
|
|
13
|
+
}
|
|
14
|
+
export function listModels() {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
capability: '3d-generation (text/image/multiview-to-model)',
|
|
18
|
+
default: MODEL_V31,
|
|
19
|
+
models: withAliases([MODEL_V31, MODEL_P2, MODEL_P1, ...LEGACY_3D_MODELS], MODEL_ALIASES),
|
|
20
|
+
},
|
|
21
|
+
{ capability: 'text-to-image', default: CAPABILITY_DEFAULTS.textToImage, models: plain(TEXT_TO_IMAGE_MODELS) },
|
|
22
|
+
{ capability: 'image-to-image', default: CAPABILITY_DEFAULTS.imageToImage, models: plain(IMAGE_TO_IMAGE_MODELS) },
|
|
23
|
+
{ capability: 'texture (model texture)', default: CAPABILITY_DEFAULTS.texture, models: plain(TEXTURE_MODELS) },
|
|
24
|
+
{
|
|
25
|
+
capability: 'rig (anim rig)',
|
|
26
|
+
default: CAPABILITY_DEFAULTS.rig,
|
|
27
|
+
models: withAliases([RIG_MODEL_V1, RIG_MODEL_V25, RIG_MODEL_V30], RIG_MODEL_ALIASES),
|
|
28
|
+
},
|
|
29
|
+
{ capability: 'segment (mesh segment)', default: CAPABILITY_DEFAULTS.segment, models: plain(SEGMENT_MODELS) },
|
|
30
|
+
{ capability: 'decimate (mesh decimate)', default: 'v2.0', models: plain(DECIMATE_MODELS) },
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function registerModelsCommand(program) {
|
|
34
|
+
program
|
|
35
|
+
.command('models')
|
|
36
|
+
.description('list the model ids each capability accepts (local whitelist, no API call)')
|
|
37
|
+
.action(() => {
|
|
38
|
+
const data = listModels();
|
|
39
|
+
const human = data
|
|
40
|
+
.map((c) => [
|
|
41
|
+
`${c.capability} (default: ${c.default})`,
|
|
42
|
+
...c.models.map((m) => ` ${m.id}${m.aliases ? ` (aliases: ${m.aliases.join(', ')})` : ''}`),
|
|
43
|
+
].join('\n'))
|
|
44
|
+
.join('\n\n');
|
|
45
|
+
emitResult(data, human);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Processing command groups: `tripo model`, `tripo anim`, `tripo mesh`.
|
|
3
|
-
* Input resolution order: explicit argument > piped NDJSON from stdin > @last.
|
|
3
|
+
* Input resolution order: explicit argument > piped NDJSON from stdin > @last (interactive terminals only).
|
|
4
4
|
* That makes both human implicit-context and shell pipelines work:
|
|
5
5
|
* tripo make cat.png | tripo anim rig | tripo anim retarget --param animation=preset:walk
|
|
6
6
|
*/
|
|
@@ -8,7 +8,6 @@ import { UsageError } from '../core/errors.js';
|
|
|
8
8
|
import { log, warn } from '../core/output.js';
|
|
9
9
|
import { buildStepRequest } from '../core/requests.js';
|
|
10
10
|
import { resolveInputValue, runTask } from '../core/task-service.js';
|
|
11
|
-
import { t } from '../i18n.js';
|
|
12
11
|
import { getStep, isMeshEditEnabled } from '../knowledge/chains.js';
|
|
13
12
|
import { readPipedTask } from '../pipeline/stdin.js';
|
|
14
13
|
import { detectInput, IMAGE_EXTS, urlExtension } from '../util/input-detect.js';
|
|
@@ -20,11 +19,11 @@ async function resolveStepInput(client, raw) {
|
|
|
20
19
|
}
|
|
21
20
|
const piped = await readPipedTask();
|
|
22
21
|
if (piped) {
|
|
23
|
-
log(
|
|
22
|
+
log(`using piped task ${piped.task_id}`);
|
|
24
23
|
return { value: piped.task_id, sourceType: piped.type };
|
|
25
24
|
}
|
|
26
25
|
const fallback = await resolveInputValue(client, '@last');
|
|
27
|
-
log(
|
|
26
|
+
log(`no input given; defaulting to the last successful task (@last)`);
|
|
28
27
|
return { value: fallback.value, sourceType: fallback.sourceTaskType };
|
|
29
28
|
}
|
|
30
29
|
function makeStepAction(stepName, extraDefaults = {}) {
|
package/dist/commands/shared.js
CHANGED
|
@@ -3,7 +3,6 @@ import { loadConfig } from '../config/config.js';
|
|
|
3
3
|
import { TripoClient } from '../core/client.js';
|
|
4
4
|
import { UsageError } from '../core/errors.js';
|
|
5
5
|
import { emitResult } from '../core/output.js';
|
|
6
|
-
import { t } from '../i18n.js';
|
|
7
6
|
import { ChainError, parseThenExpression } from '../knowledge/chains.js';
|
|
8
7
|
/** repeatable --param key=value collector */
|
|
9
8
|
export function collectParam(value, previous) {
|
|
@@ -77,32 +76,28 @@ export function summarizeCredits(tasks) {
|
|
|
77
76
|
/** Standard result output for a finished task (human + JSON modes). */
|
|
78
77
|
export function printTaskResult(result) {
|
|
79
78
|
const lines = [];
|
|
80
|
-
lines.push(
|
|
81
|
-
zh: `任务 ${result.task_id} ${result.status === 'success' ? '完成' : result.status}`,
|
|
82
|
-
en: `task ${result.task_id} ${result.status}`,
|
|
83
|
-
}));
|
|
79
|
+
lines.push(`task ${result.task_id} ${result.status}`);
|
|
84
80
|
if (result.credits_breakdown && result.credits_breakdown.length > 1) {
|
|
85
|
-
lines.push(
|
|
81
|
+
lines.push(`credits consumed (total): ${result.credits_consumed}`);
|
|
86
82
|
const typeWidth = Math.max(...result.credits_breakdown.map((l) => l.type.length));
|
|
87
83
|
for (const line of result.credits_breakdown) {
|
|
88
84
|
lines.push(` ${line.type.padEnd(typeWidth)} ${line.task_id} ${line.credits_consumed}`);
|
|
89
85
|
}
|
|
90
86
|
}
|
|
91
87
|
else if (result.credits_consumed !== undefined) {
|
|
92
|
-
lines.push(
|
|
88
|
+
lines.push(`credits consumed: ${result.credits_consumed}`);
|
|
93
89
|
}
|
|
94
90
|
if (result.output_dir) {
|
|
95
|
-
lines.push(
|
|
91
|
+
lines.push(`artifacts: ${result.output_dir}`);
|
|
96
92
|
}
|
|
97
93
|
if (result.model_file)
|
|
98
|
-
lines.push(
|
|
94
|
+
lines.push(`model file: ${result.model_file}`);
|
|
95
|
+
if (result.source_model_file)
|
|
96
|
+
lines.push(`generated model: ${result.source_model_file}`);
|
|
99
97
|
if (result.preview)
|
|
100
|
-
lines.push(
|
|
98
|
+
lines.push(`preview: ${result.preview}`);
|
|
101
99
|
if (result.normalize_scale !== undefined) {
|
|
102
|
-
lines.push(
|
|
103
|
-
zh: `导入缩放系数: ${result.normalize_scale}(原始尺寸 = 导入后尺寸 ÷ ${result.normalize_scale})`,
|
|
104
|
-
en: `import scale: ${result.normalize_scale} (original size = imported size ÷ ${result.normalize_scale})`,
|
|
105
|
-
}));
|
|
100
|
+
lines.push(`import scale: ${result.normalize_scale} (original size = imported size ÷ ${result.normalize_scale})`);
|
|
106
101
|
}
|
|
107
102
|
emitResult(result, lines.join('\n'));
|
|
108
103
|
}
|
package/dist/commands/task.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
import { readHistory, resolveReference, updateHistoryStatus } from '../config/history.js';
|
|
3
3
|
import { TripoClient } from '../core/client.js';
|
|
4
4
|
import { defaultOutputDir, downloadArtifacts } from '../core/download.js';
|
|
5
|
-
import {
|
|
5
|
+
import { emitResult, isJsonMode, isQuiet, log } from '../core/output.js';
|
|
6
6
|
import { watchTask } from '../core/watch.js';
|
|
7
7
|
import { uploadLocalFile } from '../core/upload.js';
|
|
8
|
-
import { t } from '../i18n.js';
|
|
9
8
|
import { createSpinner } from '../util/spinner.js';
|
|
10
9
|
import { newClient } from './shared.js';
|
|
11
10
|
function resolveTaskId(target) {
|
|
@@ -21,7 +20,7 @@ function humanTask(task) {
|
|
|
21
20
|
`${task.task_id} ${task.type} ${task.status}${typeof task.progress === 'number' ? ` ${task.progress}%` : ''}`,
|
|
22
21
|
];
|
|
23
22
|
if (task.credits_consumed !== undefined)
|
|
24
|
-
lines.push(
|
|
23
|
+
lines.push(`credits ${task.credits_consumed}`);
|
|
25
24
|
const output = task.output ?? {};
|
|
26
25
|
for (const [key, value] of Object.entries(output)) {
|
|
27
26
|
if (typeof value === 'string' && value.startsWith('http'))
|
|
@@ -47,7 +46,7 @@ export function registerTaskCommands(program) {
|
|
|
47
46
|
const entry = readHistory().find((e) => e.task_id === taskId);
|
|
48
47
|
const dir = opts.out ?? entry?.output_dir ?? defaultOutputDir(process.cwd(), entry?.name ?? 'task', taskId);
|
|
49
48
|
const artifacts = await downloadArtifacts(detail, dir);
|
|
50
|
-
emitResult({ ...detail, output_dir: artifacts.outputDir, files: artifacts.files }, `${humanTask(detail)}\n${
|
|
49
|
+
emitResult({ ...detail, output_dir: artifacts.outputDir, files: artifacts.files }, `${humanTask(detail)}\n${`artifacts: ${artifacts.outputDir}`}`);
|
|
51
50
|
return;
|
|
52
51
|
}
|
|
53
52
|
emitResult(detail, humanTask(detail));
|
|
@@ -62,19 +61,19 @@ export function registerTaskCommands(program) {
|
|
|
62
61
|
? targets.map(resolveTaskId)
|
|
63
62
|
: readHistory().slice(-opts.limit).map((e) => e.task_id);
|
|
64
63
|
if (ids.length === 0) {
|
|
65
|
-
emitResult([],
|
|
64
|
+
emitResult([], 'no local history yet');
|
|
66
65
|
return;
|
|
67
66
|
}
|
|
68
67
|
const { tasks, missed } = await client.listTasks(ids);
|
|
69
68
|
const lines = tasks.map((d) => `${d.task_id} ${(d.type ?? '').padEnd(24)} ${d.status}${typeof d.progress === 'number' ? ` ${d.progress}%` : ''}`);
|
|
70
69
|
if (missed.length > 0) {
|
|
71
|
-
lines.push(
|
|
70
|
+
lines.push(`missed: ${missed.join(', ')}`);
|
|
72
71
|
}
|
|
73
72
|
emitResult({ tasks, missed }, lines.join('\n'));
|
|
74
73
|
});
|
|
75
74
|
task
|
|
76
75
|
.command('watch <task-id>')
|
|
77
|
-
.description('block until a task finishes (
|
|
76
|
+
.description('block until a task finishes (progress on stderr; stdout carries only the final JSON)')
|
|
78
77
|
.option('--download', 'download artifacts on success')
|
|
79
78
|
.option('-o, --out <dir>', 'output directory for --download')
|
|
80
79
|
.option('--timeout <seconds>', 'timeout in seconds', (v) => Number(v))
|
|
@@ -91,7 +90,7 @@ export function registerTaskCommands(program) {
|
|
|
91
90
|
}
|
|
92
91
|
else if (isJsonMode() && typeof d.progress === 'number' && d.progress !== lastProgress) {
|
|
93
92
|
lastProgress = d.progress;
|
|
94
|
-
|
|
93
|
+
log(`${d.type ?? 'task'} · ${d.status} ${d.progress}%`);
|
|
95
94
|
}
|
|
96
95
|
},
|
|
97
96
|
});
|
|
@@ -100,9 +99,9 @@ export function registerTaskCommands(program) {
|
|
|
100
99
|
if (opts.download) {
|
|
101
100
|
const entry = readHistory().find((e) => e.task_id === taskId);
|
|
102
101
|
const dir = opts.out ?? entry?.output_dir ?? defaultOutputDir(process.cwd(), entry?.name ?? 'task', taskId);
|
|
103
|
-
log(
|
|
102
|
+
log('downloading artifacts…');
|
|
104
103
|
const artifacts = await downloadArtifacts(detail, dir);
|
|
105
|
-
emitResult({ ...detail, output_dir: artifacts.outputDir, files: artifacts.files, model_file: artifacts.modelFile, preview: artifacts.preview }, `${humanTask(detail)}\n${
|
|
104
|
+
emitResult({ ...detail, output_dir: artifacts.outputDir, files: artifacts.files, model_file: artifacts.modelFile, preview: artifacts.preview }, `${humanTask(detail)}\n${`artifacts: ${artifacts.outputDir}`}`);
|
|
106
105
|
return;
|
|
107
106
|
}
|
|
108
107
|
emitResult(detail, humanTask(detail));
|
|
@@ -125,6 +124,6 @@ export function registerTaskCommands(program) {
|
|
|
125
124
|
const human = entries
|
|
126
125
|
.map((e) => `${e.created_at.slice(0, 19)} ${(e.name ?? '').padEnd(24)} ${e.type.padEnd(22)} ${e.status.padEnd(9)} ${e.task_id}`)
|
|
127
126
|
.join('\n');
|
|
128
|
-
emitResult(entries, human ||
|
|
127
|
+
emitResult(entries, human || 'no local history yet');
|
|
129
128
|
});
|
|
130
129
|
}
|
package/dist/commands/topup.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { sleep, TripoClient } from '../core/client.js';
|
|
7
7
|
import { topupUrl } from '../core/task-service.js';
|
|
8
8
|
import { emitResult, log, shouldOpen } from '../core/output.js';
|
|
9
|
-
import { t } from '../i18n.js';
|
|
10
9
|
import { createSpinner } from '../util/spinner.js';
|
|
11
10
|
import { openExternal } from '../util/open.js';
|
|
12
11
|
const POLL_INTERVAL_MS = 3000;
|
|
@@ -22,17 +21,17 @@ export function registerTopupCommand(program) {
|
|
|
22
21
|
const url = topupUrl();
|
|
23
22
|
if (shouldOpen()) {
|
|
24
23
|
openExternal(url);
|
|
25
|
-
log(
|
|
24
|
+
log(`opened the billing page: ${url}`);
|
|
26
25
|
}
|
|
27
26
|
else {
|
|
28
|
-
log(
|
|
27
|
+
log(`billing page: ${url}`);
|
|
29
28
|
}
|
|
30
|
-
log(
|
|
29
|
+
log(`current balance: ${before.balance} credits`);
|
|
31
30
|
if (!opts.watch || !shouldOpen()) {
|
|
32
31
|
emitResult({ billing_url: url, balance: before.balance }, url);
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
35
|
-
const spinner = createSpinner(
|
|
34
|
+
const spinner = createSpinner('waiting for credits… (Ctrl+C is safe; payment continues in the browser)');
|
|
36
35
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
37
36
|
let current = Number(before.balance);
|
|
38
37
|
while (Date.now() < deadline) {
|
|
@@ -41,10 +40,7 @@ export function registerTopupCommand(program) {
|
|
|
41
40
|
const now = await client.getBalance();
|
|
42
41
|
const value = Number(now.balance);
|
|
43
42
|
if (value > current) {
|
|
44
|
-
spinner.succeed(
|
|
45
|
-
zh: `到账 +${value - current} 积分,当前余额 ${value}`,
|
|
46
|
-
en: `+${value - current} credits arrived; balance is now ${value}`,
|
|
47
|
-
}));
|
|
43
|
+
spinner.succeed(`+${value - current} credits arrived; balance is now ${value}`);
|
|
48
44
|
emitResult({ billing_url: url, balance: value, delta: value - current });
|
|
49
45
|
return;
|
|
50
46
|
}
|
|
@@ -55,7 +51,7 @@ export function registerTopupCommand(program) {
|
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
spinner.stop();
|
|
58
|
-
log(
|
|
54
|
+
log('no balance change detected within 10 minutes; credits apply automatically once payment completes');
|
|
59
55
|
emitResult({ billing_url: url, balance: current });
|
|
60
56
|
});
|
|
61
57
|
}
|
package/dist/commands/update.js
CHANGED
|
@@ -15,7 +15,6 @@ import pc from 'picocolors';
|
|
|
15
15
|
import { tripoHome } from '../config/config.js';
|
|
16
16
|
import { CliError, NetworkError } from '../core/errors.js';
|
|
17
17
|
import { emitResult, isJsonMode, log, success } from '../core/output.js';
|
|
18
|
-
import { t } from '../i18n.js';
|
|
19
18
|
import { EXIT } from '../knowledge/error-catalog.js';
|
|
20
19
|
import { cliVersion } from '../version.js';
|
|
21
20
|
const PACKAGE_NAME = 'tripo-cli';
|
|
@@ -87,10 +86,7 @@ export function notifyIfOutdated() {
|
|
|
87
86
|
const current = cliVersion();
|
|
88
87
|
const cache = readCache();
|
|
89
88
|
if (cache && isNewerVersion(cache.latest, current)) {
|
|
90
|
-
process.stderr.write(pc.yellow(
|
|
91
|
-
zh: `! 有新版本 tripo-cli ${cache.latest} 可用(当前 ${current}),运行 tripo update 升级`,
|
|
92
|
-
en: `! a new version of tripo-cli is available: ${cache.latest} (current ${current}) — run "tripo update" to upgrade`,
|
|
93
|
-
})) + '\n');
|
|
89
|
+
process.stderr.write(pc.yellow(`! a new version of tripo-cli is available: ${cache.latest} (current ${current}) — run "tripo update" to upgrade`) + '\n');
|
|
94
90
|
}
|
|
95
91
|
if (!cache || Date.now() - cache.checked_at > CHECK_INTERVAL_MS) {
|
|
96
92
|
// Fire-and-forget; the result shows up on the next run.
|
|
@@ -123,7 +119,7 @@ function runNpmInstall() {
|
|
|
123
119
|
if (code === 0)
|
|
124
120
|
resolve();
|
|
125
121
|
else
|
|
126
|
-
reject(new CliError(`npm install exited with code ${code}`, EXIT.FATAL,
|
|
122
|
+
reject(new CliError(`npm install exited with code ${code}`, EXIT.FATAL, `run manually: ${manual}`));
|
|
127
123
|
});
|
|
128
124
|
});
|
|
129
125
|
}
|
|
@@ -137,18 +133,18 @@ export function registerUpdateCommand(program) {
|
|
|
137
133
|
const latest = await fetchLatestVersion();
|
|
138
134
|
const available = isNewerVersion(latest, current);
|
|
139
135
|
if (!available) {
|
|
140
|
-
success(
|
|
136
|
+
success(`already up to date (${current})`);
|
|
141
137
|
emitResult({ current, latest, update_available: false, updated: false }, current);
|
|
142
138
|
return;
|
|
143
139
|
}
|
|
144
140
|
if (opts.check) {
|
|
145
|
-
log(
|
|
141
|
+
log(`new version ${latest} available (current ${current}); run "tripo update" to upgrade`);
|
|
146
142
|
emitResult({ current, latest, update_available: true, updated: false }, `${current} → ${latest}`);
|
|
147
143
|
return;
|
|
148
144
|
}
|
|
149
|
-
log(
|
|
145
|
+
log(`updating ${current} → ${latest}…`);
|
|
150
146
|
await runNpmInstall();
|
|
151
|
-
success(
|
|
147
|
+
success(`updated to tripo-cli ${latest}`);
|
|
152
148
|
emitResult({ current, latest, update_available: true, updated: true }, `${current} → ${latest}`);
|
|
153
149
|
});
|
|
154
150
|
}
|
package/dist/commands/view.js
CHANGED
|
@@ -10,7 +10,6 @@ import { downloadArtifacts, defaultOutputDir } from '../core/download.js';
|
|
|
10
10
|
import { UsageError } from '../core/errors.js';
|
|
11
11
|
import { emitResult, log, shouldOpen } from '../core/output.js';
|
|
12
12
|
import { startViewer } from '../core/viewer.js';
|
|
13
|
-
import { t } from '../i18n.js';
|
|
14
13
|
import { runTask } from '../core/task-service.js';
|
|
15
14
|
import { openExternal } from '../util/open.js';
|
|
16
15
|
import { printTaskResult, newClient } from './shared.js';
|
|
@@ -26,11 +25,11 @@ export function registerViewCommands(program) {
|
|
|
26
25
|
const handle = await startViewer(modelFile, target);
|
|
27
26
|
if (shouldOpen()) {
|
|
28
27
|
openExternal(handle.url);
|
|
29
|
-
log(
|
|
28
|
+
log(`preview opened: ${handle.url}`);
|
|
30
29
|
}
|
|
31
30
|
emitResult({ url: handle.url, model_file: modelFile }, `${handle.url}\n${modelFile}`);
|
|
32
31
|
if (opts.keep || shouldOpen()) {
|
|
33
|
-
log(
|
|
32
|
+
log('Ctrl+C to stop the preview server');
|
|
34
33
|
await new Promise(() => { }); // hold until interrupted
|
|
35
34
|
}
|
|
36
35
|
else {
|
|
@@ -84,7 +83,7 @@ async function resolveModelFile(target) {
|
|
|
84
83
|
const task = await client.getTask(taskId);
|
|
85
84
|
if (task.status !== 'success')
|
|
86
85
|
throw new UsageError(`task ${taskId} is ${task.status}, nothing to view yet`);
|
|
87
|
-
log(
|
|
86
|
+
log('downloading the model for preview…');
|
|
88
87
|
const dir = entry?.output_dir ?? defaultOutputDir(process.cwd(), entry?.name ?? 'view', taskId);
|
|
89
88
|
const artifacts = await downloadArtifacts(task, dir);
|
|
90
89
|
if (!artifacts.modelFile || !VIEWABLE.has(extname(artifacts.modelFile).toLowerCase())) {
|
package/dist/config/config.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Multi-account model: credentials live in named *profiles* (a dict plus an
|
|
5
5
|
* `active_profile` pointer — same shape as AWS CLI profiles / kubectl
|
|
6
6
|
* contexts). Everything account-bound (key, region, URL overrides) is
|
|
7
|
-
* per-profile; user preferences (
|
|
7
|
+
* per-profile; user preferences (llm, output dir) stay top-level.
|
|
8
8
|
*
|
|
9
9
|
* v0.1.0 stored a single key at the top level. That format is normalized
|
|
10
10
|
* into `profiles.default` on *every* load, so old files keep working
|
package/dist/core/client.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* server's Retry-After / X-RateLimit-Reset hint, capped at 60s per wait.
|
|
7
7
|
*/
|
|
8
8
|
import { activeProfileName, DEFAULT_PROFILE, listProfiles, loadConfig, resolveApiBaseUrl, resolveApiKey } from '../config/config.js';
|
|
9
|
-
import { t } from '../i18n.js';
|
|
10
9
|
import { EXIT } from '../knowledge/error-catalog.js';
|
|
11
10
|
import { ApiError, AuthError, NetworkError } from './errors.js';
|
|
12
11
|
import { identifyUser, observeTask, trackEvent, taskOrigin } from './analytics.js';
|
|
@@ -28,15 +27,9 @@ export class TripoClient {
|
|
|
28
27
|
const wanted = activeProfileName(config);
|
|
29
28
|
const known = listProfiles(config).map((p) => p.name);
|
|
30
29
|
if (wanted !== DEFAULT_PROFILE && !known.includes(wanted)) {
|
|
31
|
-
throw new AuthError(
|
|
32
|
-
zh: `没有叫「${wanted}」的 profile${known.length ? `(现有: ${known.join(', ')})` : ''}`,
|
|
33
|
-
en: `profile "${wanted}" not found${known.length ? ` (have: ${known.join(', ')})` : ''}`,
|
|
34
|
-
}), t({
|
|
35
|
-
zh: `用 tripo login --profile ${wanted} 登录,或 tripo use 切换`,
|
|
36
|
-
en: `run "tripo login --profile ${wanted}" to create it, or "tripo use" to switch`,
|
|
37
|
-
}));
|
|
30
|
+
throw new AuthError(`profile "${wanted}" not found${known.length ? ` (have: ${known.join(', ')})` : ''}`, `run "tripo login --profile ${wanted}" to create it, or "tripo use" to switch`);
|
|
38
31
|
}
|
|
39
|
-
throw new AuthError(
|
|
32
|
+
throw new AuthError('no API key configured');
|
|
40
33
|
}
|
|
41
34
|
this.apiKey = key;
|
|
42
35
|
this.timeoutMs = options.timeoutMs ?? 60_000;
|
|
@@ -188,10 +181,8 @@ export class TripoClient {
|
|
|
188
181
|
}
|
|
189
182
|
catch (err) {
|
|
190
183
|
if (err instanceof NetworkError || (err instanceof ApiError && err.exitCode === EXIT.NETWORK)) {
|
|
191
|
-
err.suggestion =
|
|
192
|
-
|
|
193
|
-
en: 'the task may already exist server-side (credits frozen); check "tripo balance" or the console before retrying, to avoid a duplicate charge',
|
|
194
|
-
});
|
|
184
|
+
err.suggestion = 'the task may already exist server-side (credits frozen); check "tripo balance" or the console before retrying, to avoid a duplicate charge';
|
|
185
|
+
err.submissionUncertain = true;
|
|
195
186
|
}
|
|
196
187
|
throw err;
|
|
197
188
|
}
|