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
@@ -16,7 +16,6 @@ import { checkDeviceApproval, clearPendingGrant, DeviceAuthUnavailableError, loa
16
16
  import { AuthError, CliError, NetworkError, UsageError } from '../core/errors.js';
17
17
  import { detectRegionForKey } from '../core/region.js';
18
18
  import { emitResult, isInteractive, log, shouldOpen, success, warn } from '../core/output.js';
19
- import { t } from '../i18n.js';
20
19
  import { EXIT } from '../knowledge/error-catalog.js';
21
20
  import { openExternal } from '../util/open.js';
22
21
  import { createSpinner } from '../util/spinner.js';
@@ -40,22 +39,16 @@ export function registerAuthCommands(program) {
40
39
  .action((opts) => {
41
40
  const name = opts.profile ?? activeProfileName();
42
41
  if (!deleteProfile(name)) {
43
- log(t({ zh: `Profile「${name}」本地没有存储 API Key`, en: `no API key stored locally for profile "${name}"` }));
42
+ log(`no API key stored locally for profile "${name}"`);
44
43
  }
45
44
  else {
46
- success(t({ zh: `已删除 profile「${name}」的本地 API Key`, en: `local API key removed (profile "${name}")` }));
45
+ success(`local API key removed (profile "${name}")`);
47
46
  const rest = listProfiles();
48
47
  if (rest.length > 0) {
49
- log(t({
50
- zh: `其余账号仍在: ${rest.map((x) => x.name).join(', ')}(tripo use 切换)`,
51
- en: `remaining profiles: ${rest.map((x) => x.name).join(', ')} (switch with "tripo use")`,
52
- }));
48
+ log(`remaining profiles: ${rest.map((x) => x.name).join(', ')} (switch with "tripo use")`);
53
49
  }
54
50
  }
55
- log(t({
56
- zh: `如需吊销 Key 本身,请前往 ${resolvePlatformBaseUrl()} 的 API Keys 页面`,
57
- en: `to revoke the key itself, visit the API Keys page at ${resolvePlatformBaseUrl()}`,
58
- }));
51
+ log(`to revoke the key itself, visit the API Keys page at ${resolvePlatformBaseUrl()}`);
59
52
  });
60
53
  program
61
54
  .command('whoami')
@@ -74,13 +67,10 @@ export function registerAuthCommands(program) {
74
67
  `key ${maskKey(key)} (${source})`,
75
68
  `region ${region}`,
76
69
  `api ${client.baseUrl}`,
77
- t({ zh: `余额 ${balance.balance} 积分(冻结 ${balance.frozen})`, en: `balance ${balance.balance} credits (${balance.frozen} frozen)` }),
70
+ `balance ${balance.balance} credits (${balance.frozen} frozen)`,
78
71
  ];
79
72
  if (others.length > 0) {
80
- lines.push(t({
81
- zh: `其它账号 ${others.map((x) => profileLabel(x.name, x.profile.region)).join(', ')}(tripo use 切换)`,
82
- en: `others ${others.map((x) => profileLabel(x.name, x.profile.region)).join(', ')} (switch with "tripo use")`,
83
- }));
73
+ lines.push(`others ${others.map((x) => profileLabel(x.name, x.profile.region)).join(', ')} (switch with "tripo use")`);
84
74
  }
85
75
  emitResult({
86
76
  key: maskKey(key),
@@ -106,21 +96,18 @@ function profileLabel(name, region) {
106
96
  async function runUse(name) {
107
97
  const profiles = listProfiles();
108
98
  if (profiles.length === 0) {
109
- throw new AuthError(t({ zh: '还没有任何账号', en: 'no profiles yet' }), t({ zh: '先运行 tripo login 登录', en: 'run "tripo login" first' }));
99
+ throw new AuthError('no profiles yet', 'run "tripo login" first');
110
100
  }
111
101
  if (!name) {
112
102
  if (!isInteractive()) {
113
- throw new UsageError(t({
114
- zh: `非交互环境请指定名字: tripo use <${profiles.map((x) => x.name).join('|')}>`,
115
- en: `specify a profile in non-interactive mode: tripo use <${profiles.map((x) => x.name).join('|')}>`,
116
- }));
103
+ throw new UsageError(`specify a profile in non-interactive mode: tripo use <${profiles.map((x) => x.name).join('|')}>`);
117
104
  }
118
105
  const answer = await p.select({
119
- message: t({ zh: '切换到哪个账号?', en: 'Switch to which account?' }),
106
+ message: 'Switch to which account?',
120
107
  initialValue: profiles.find((x) => x.active)?.name,
121
108
  options: profiles.map((x) => ({
122
109
  value: x.name,
123
- label: profileLabel(x.name, x.profile.region) + (x.active ? t({ zh: '(当前)', en: ' (current)' }) : ''),
110
+ label: profileLabel(x.name, x.profile.region) + (x.active ? ' (current)' : ''),
124
111
  hint: x.profile.api_key ? maskKey(x.profile.api_key) : undefined,
125
112
  })),
126
113
  });
@@ -130,21 +117,12 @@ async function runUse(name) {
130
117
  }
131
118
  const match = profiles.find((x) => x.name === name);
132
119
  if (!match) {
133
- throw new UsageError(t({
134
- zh: `没有叫「${name}」的 profile(现有: ${profiles.map((x) => x.name).join(', ')})`,
135
- en: `no profile named "${name}" (have: ${profiles.map((x) => x.name).join(', ')})`,
136
- }), t({ zh: `新账号用 tripo login --profile ${name} 登录`, en: `log a new account in with: tripo login --profile ${name}` }));
120
+ throw new UsageError(`no profile named "${name}" (have: ${profiles.map((x) => x.name).join(', ')})`, `log a new account in with: tripo login --profile ${name}`);
137
121
  }
138
122
  setActiveProfile(name);
139
- success(t({
140
- zh: `已切换到「${name}」${match.profile.region ? `(region=${match.profile.region})` : ''}`,
141
- en: `switched to "${name}"${match.profile.region ? ` (region=${match.profile.region})` : ''}`,
142
- }));
123
+ success(`switched to "${name}"${match.profile.region ? ` (region=${match.profile.region})` : ''}`);
143
124
  if (process.env.TRIPO_API_KEY) {
144
- warn(t({
145
- zh: '注意:环境变量 TRIPO_API_KEY 仍然优先于 profile,如需使用 profile 请先 unset',
146
- en: 'note: TRIPO_API_KEY env var still overrides the profile — unset it to use the profile key',
147
- }));
125
+ warn('note: TRIPO_API_KEY env var still overrides the profile — unset it to use the profile key');
148
126
  }
149
127
  }
150
128
  export async function runLogin(opts) {
@@ -153,17 +131,11 @@ export async function runLogin(opts) {
153
131
  // re-authenticates work, it does not silently overwrite default.
154
132
  const profile = opts.profile ?? activeProfileName();
155
133
  if (!isValidProfileName(profile)) {
156
- throw new UsageError(t({
157
- zh: `profile 名「${profile}」不合法(小写字母/数字开头,可含 - _,最长 32)`,
158
- en: `invalid profile name "${profile}" (lowercase alphanumeric start, may contain - _, max 32 chars)`,
159
- }));
134
+ throw new UsageError(`invalid profile name "${profile}" (lowercase alphanumeric start, may contain - _, max 32 chars)`);
160
135
  }
161
136
  const replacing = loadConfig().profiles?.[profile]?.api_key;
162
137
  if (replacing) {
163
- log(t({
164
- zh: `将替换 profile「${profile}」现有的 Key ${maskKey(replacing)}(新账号请用 tripo login --profile 名字)`,
165
- en: `this will replace the key ${maskKey(replacing)} in profile "${profile}" (for a second account use: tripo login --profile <name>)`,
166
- }));
138
+ log(`this will replace the key ${maskKey(replacing)} in profile "${profile}" (for a second account use: tripo login --profile <name>)`);
167
139
  }
168
140
  if (opts.key) {
169
141
  // Key in hand → region is detected, not asked (a key lives in exactly one region).
@@ -183,14 +155,11 @@ export async function runLogin(opts) {
183
155
  // key's real region wins.
184
156
  if (!region) {
185
157
  const answer = await p.select({
186
- message: t({
187
- zh: '你的账号在哪个站?(选错没关系,验证 Key 时会自动纠正)',
188
- en: 'Where is your account? (a wrong pick self-corrects once the key is verified)',
189
- }),
158
+ message: 'Where is your account? (a wrong pick self-corrects once the key is verified)',
190
159
  initialValue: guessRegion(),
191
160
  options: [
192
- { value: 'ov', label: t({ zh: '海外站 developers.tripo3d.ai(Stripe 支付)', en: 'International — developers.tripo3d.ai (Stripe)' }) },
193
- { value: 'cn', label: t({ zh: '国内站 developers.tripo3d.com(支付宝)', en: 'China mainland — developers.tripo3d.com (Alipay)' }) },
161
+ { value: 'ov', label: 'International — developers.tripo3d.ai (Stripe)' },
162
+ { value: 'cn', label: 'China mainland — developers.tripo3d.com (Alipay)' },
194
163
  ],
195
164
  });
196
165
  if (p.isCancel(answer))
@@ -199,10 +168,7 @@ export async function runLogin(opts) {
199
168
  }
200
169
  const key = opts.paste ? await keyFromConsole(region, false) : await deviceFlowWithFallback(region, profile);
201
170
  await validateAndSave(key, region, profile);
202
- p.outro(t({
203
- zh: '登录完成!试试: tripo make "一只可爱的机器人"',
204
- en: 'logged in! try: tripo make "a cute robot"',
205
- }));
171
+ p.outro('logged in! try: tripo make "a cute robot"');
206
172
  }
207
173
  /**
208
174
  * Non-interactive login (agents, CI without a provisioned key): the same
@@ -220,19 +186,10 @@ export async function runLogin(opts) {
220
186
  */
221
187
  async function runHeadlessLogin(region, profile, paste, noWait) {
222
188
  if (paste) {
223
- throw new UsageError(t({
224
- zh: '--paste 需要交互终端;非交互环境请用设备码登录(tripo login --region ov|cn)或 TRIPO_API_KEY / --key',
225
- en: '--paste needs an interactive terminal — use device login ("tripo login --region ov|cn"), TRIPO_API_KEY, or --key instead',
226
- }));
189
+ throw new UsageError('--paste needs an interactive terminal — use device login ("tripo login --region ov|cn"), TRIPO_API_KEY, or --key instead');
227
190
  }
228
191
  if (!region) {
229
- throw new UsageError(t({
230
- zh: `非交互登录需要 --region:先问用户账号在哪个站,不要猜(授权只在该站有效)— cn = ${siteLabel('cn')};ov = ${siteLabel('ov')};还没有账号:中国大陆选 cn,其它地区选 ov`,
231
- en: `non-interactive login needs --region: ask the human which site their account is on, do not guess (authorization only works on that site) — cn = ${siteLabel('cn')}; ov = ${siteLabel('ov')}; no account yet: mainland China → cn, elsewhere → ov`,
232
- }), t({
233
- zh: 'tripo login --region cn|ov;已有 Key 时用 tripo login --key tsk_...(自动识别站点)',
234
- en: '"tripo login --region cn|ov" — or, with a key in hand: "tripo login --key tsk_..." (site auto-detected)',
235
- }));
192
+ throw new UsageError(`non-interactive login needs --region: ask the human which site their account is on, do not guess (authorization only works on that site) — cn = ${siteLabel('cn')}; ov = ${siteLabel('ov')}; no account yet: mainland China → cn, elsewhere → ov`, '"tripo login --region cn|ov" — or, with a key in hand: "tripo login --key tsk_..." (site auto-detected)');
236
193
  }
237
194
  let grant = loadPendingGrant(region, profile);
238
195
  const resumed = grant !== undefined;
@@ -242,10 +199,7 @@ async function runHeadlessLogin(region, profile, paste, noWait) {
242
199
  poll = await checkDeviceApproval(region, grant);
243
200
  }
244
201
  catch (err) {
245
- throw new NetworkError(t({
246
- zh: `授权服务暂时连不上(${err.message}),终端码仍然有效`,
247
- en: `authorization service unavailable (${err.message}); the code is still valid`,
248
- }), t({ zh: '稍后重新运行同一条命令', en: 'run the same command again shortly' }));
202
+ throw new NetworkError(`authorization service unavailable (${err.message}); the code is still valid`, 'run the same command again shortly');
249
203
  }
250
204
  if (poll.status !== 'pending') {
251
205
  clearPendingGrant();
@@ -260,16 +214,10 @@ async function runHeadlessLogin(region, profile, paste, noWait) {
260
214
  catch (err) {
261
215
  if (err instanceof DeviceAuthUnavailableError) {
262
216
  if (err.kind === 'unreachable') {
263
- throw new CliError(t({
264
- zh: `连不上授权服务(${err.message})— 请检查本机网络/防火墙,出网走 HTTP 代理时设置 HTTPS_PROXY;或改用 TRIPO_API_KEY / tripo login --key tsk_...`,
265
- en: `cannot reach the authorization service (${err.message}) — check this machine's network/firewall (set HTTPS_PROXY when behind a proxy), or use TRIPO_API_KEY / "tripo login --key tsk_..."`,
266
- }), EXIT.NETWORK);
217
+ throw new CliError(`cannot reach the authorization service (${err.message}) — check this machine's network/firewall (set HTTPS_PROXY when behind a proxy), or use TRIPO_API_KEY / "tripo login --key tsk_..."`, EXIT.NETWORK);
267
218
  }
268
219
  // No paste fallback without a TTY — fail with every remaining option spelled out.
269
- throw new CliError(t({
270
- zh: `浏览器授权服务不可用(${err.message});请设置 TRIPO_API_KEY,运行 tripo login --key tsk_...,或在交互终端里运行 tripo login`,
271
- en: `device authorization unavailable (${err.message}) — set TRIPO_API_KEY, run "tripo login --key tsk_...", or run "tripo login" in an interactive terminal`,
272
- }), EXIT.AUTH);
220
+ throw new CliError(`device authorization unavailable (${err.message}) — set TRIPO_API_KEY, run "tripo login --key tsk_...", or run "tripo login" in an interactive terminal`, EXIT.AUTH);
273
221
  }
274
222
  throw err;
275
223
  }
@@ -278,37 +226,18 @@ async function runHeadlessLogin(region, profile, paste, noWait) {
278
226
  const minutes = Math.max(1, Math.round(grant.expires_in / 60));
279
227
  const rerun = `tripo login --region ${region}${profile === DEFAULT_PROFILE ? '' : ` --profile ${profile}`}${noWait ? ' --no-wait' : ''}`;
280
228
  const header = noWait
281
- ? {
282
- zh: `需要在浏览器完成授权;本命令现在退出。用户说授权完成后,运行 ${rerun} 完成登录(同一个终端码,不要重新发起)。请转告用户:`,
283
- en: `Authorization happens in a browser; this command exits now. Once the human says it is done, run ${rerun} to finish (same code — do not start over). Relay these steps:`,
284
- }
229
+ ? `Authorization happens in a browser; this command exits now. Once the human says it is done, run ${rerun} to finish (same code — do not start over). Relay these steps:`
285
230
  : resumed
286
- ? {
287
- zh: '继续等待之前发起的授权(终端码不变),直到授权完成或过期。用户还没操作的话,请转告:',
288
- en: 'Resuming the authorization started earlier (same code); waiting until approved or expired. If the human has not done it yet, relay:',
289
- }
290
- : {
291
- zh: '需要在浏览器完成授权;本命令会一直等待,直到授权完成或过期。请转告用户:',
292
- en: 'Authorization happens in a browser; this command keeps waiting until approved or expired. Relay these steps:',
293
- };
294
- log(t({
295
- zh: [
296
- header.zh,
297
- ` 1. 打开: ${grant.verification_url_complete}`,
298
- ' (页面如果提示登录,先登录或注册 Tripo 账号 — 新账号有免费额度)',
299
- ` 2. 在「Verification code」输入框填入终端码: ${grant.user_code}`,
300
- ' 3. 账号已有多把 API Key 时页面会让用户选择一把;一把没有则自动创建',
301
- ` 4. 点击「Authorize」。此码 ${minutes} 分钟内有效`,
302
- ].join('\n'),
303
- en: [
304
- header.en,
305
- ` 1. Open: ${grant.verification_url_complete}`,
306
- ' (if the page asks to sign in, sign in or sign up first — new accounts get free credits)',
307
- ` 2. Enter this one-time code in the "Verification code" field: ${grant.user_code}`,
308
- ' 3. An account with several API keys is asked to pick one; with none, a key is created automatically',
309
- ` 4. Click "Authorize". The code expires in ${minutes} minutes`,
310
- ].join('\n'),
311
- }));
231
+ ? 'Resuming the authorization started earlier (same code); waiting until approved or expired. If the human has not done it yet, relay:'
232
+ : 'Authorization happens in a browser; this command keeps waiting until approved or expired. Relay these steps:';
233
+ log([
234
+ header,
235
+ ` 1. Open: ${grant.verification_url_complete}`,
236
+ ' (if the page asks to sign in, sign in or sign up first — new accounts get free credits)',
237
+ ` 2. Enter this one-time code in the "Verification code" field: ${grant.user_code}`,
238
+ ' 3. An account with several API keys is asked to pick one; with none, a key is created automatically',
239
+ ` 4. Click "Authorize". The code expires in ${minutes} minutes`,
240
+ ].join('\n'));
312
241
  if (noWait) {
313
242
  emitResult({
314
243
  status: 'pending',
@@ -318,7 +247,7 @@ async function runHeadlessLogin(region, profile, paste, noWait) {
318
247
  verification_url: grant.verification_url_complete,
319
248
  expires_in: grant.expires_in,
320
249
  next: rerun,
321
- }, t({ zh: `等待用户授权(终端码 ${grant.user_code});完成后运行: ${rerun}`, en: `waiting for approval (code ${grant.user_code}); then run: ${rerun}` }));
250
+ }, `waiting for approval (code ${grant.user_code}); then run: ${rerun}`);
322
251
  return;
323
252
  }
324
253
  const result = await waitForDeviceApproval(region, grant);
@@ -327,19 +256,19 @@ async function runHeadlessLogin(region, profile, paste, noWait) {
327
256
  }
328
257
  async function finishDeviceLogin(result, region, profile) {
329
258
  if (result.status === 'denied') {
330
- throw new CliError(t({ zh: '授权被拒绝;重新运行 tripo login 再试', en: 'authorization denied — run "tripo login" again to retry' }), EXIT.AUTH);
259
+ throw new CliError('authorization denied — run "tripo login" again to retry', EXIT.AUTH);
331
260
  }
332
261
  if (result.status !== 'approved') {
333
- throw new CliError(t({ zh: '授权超时或已过期;重新运行 tripo login 获取新的终端码', en: 'authorization expired — run "tripo login" again for a fresh code' }), EXIT.AUTH);
262
+ throw new CliError('authorization expired — run "tripo login" again for a fresh code', EXIT.AUTH);
334
263
  }
335
264
  const saved = await validateAndSave(result.apikey, region, profile);
336
- emitResult({ status: 'logged_in', profile, region: saved.region, key: maskKey(result.apikey), balance: saved.balance }, t({ zh: `登录完成(profile=${profile}, region=${saved.region})`, en: `logged in (profile=${profile}, region=${saved.region})` }));
265
+ emitResult({ status: 'logged_in', profile, region: saved.region, key: maskKey(result.apikey), balance: saved.balance }, `logged in (profile=${profile}, region=${saved.region})`);
337
266
  }
338
267
  /** What a human recognizes a console by — they know their sign-in method and how they pay, rarely the region code. */
339
268
  function siteLabel(region) {
340
269
  return region === 'cn'
341
- ? t({ zh: '国内站 developers.tripo3d.com(手机号登录,支付宝)', en: 'China mainland site developers.tripo3d.com (phone/SMS sign-in, Alipay)' })
342
- : t({ zh: '海外站 developers.tripo3d.ai(邮箱登录,Stripe)', en: 'international site developers.tripo3d.ai (email sign-in, Stripe)' });
270
+ ? 'China mainland site developers.tripo3d.com (phone/SMS sign-in, Alipay)'
271
+ : 'international site developers.tripo3d.ai (email sign-in, Stripe)';
343
272
  }
344
273
  /**
345
274
  * Preferred path: RFC 8628 device flow — the browser does the sign-in and a
@@ -360,21 +289,15 @@ async function deviceFlowWithFallback(region, profile) {
360
289
  if (err.kind === 'backend_error') {
361
290
  // Deployed but broken: fall back so the user is not blocked, but
362
291
  // report the failure loudly — a masked 5xx is undebuggable.
363
- warn(t({
364
- zh: `浏览器授权服务故障(${err.message}),请把此错误报给平台;已改用粘贴方式登录`,
365
- en: `device authorization backend error (${err.message}) — please report this; falling back to paste login`,
366
- }));
292
+ warn(`device authorization backend error (${err.message}) — please report this; falling back to paste login`);
367
293
  }
368
294
  else if (err.kind === 'unreachable') {
369
295
  // The console API could not be reached at all — the same network
370
296
  // problem will likely hit key validation next, so say it loudly now.
371
- warn(t({
372
- zh: `连不上授权服务(${err.message})— 疑似网络/代理问题(出网走 HTTP 代理时请设置 HTTPS_PROXY);已改用粘贴方式登录`,
373
- en: `cannot reach the authorization service (${err.message}) — likely a network/proxy issue (set HTTPS_PROXY if this machine needs a proxy); falling back to paste login`,
374
- }));
297
+ warn(`cannot reach the authorization service (${err.message}) — likely a network/proxy issue (set HTTPS_PROXY if this machine needs a proxy); falling back to paste login`);
375
298
  }
376
299
  else {
377
- log(t({ zh: '浏览器授权服务暂未开通,改用粘贴方式登录', en: 'device authorization not available yet — falling back to paste login' }));
300
+ log('device authorization not available yet — falling back to paste login');
378
301
  }
379
302
  return keyFromConsole(region, true);
380
303
  }
@@ -384,43 +307,29 @@ async function deviceFlowWithFallback(region, profile) {
384
307
  // must already be on screen when the user lands on the login page — the
385
308
  // page deliberately never pre-fills the code (anti-phishing), so typing it
386
309
  // is the one thing the user has to do and the one thing we spell out.
387
- p.note(grant.user_code, t({ zh: '你的终端码', en: 'Your one-time code' }));
310
+ p.note(grant.user_code, 'Your one-time code');
388
311
  const willOpen = shouldOpen();
389
- log(t({
390
- zh: [
391
- '接下来三步:',
392
- willOpen ? ' 1. 浏览器即将打开授权页面(如未登录,先登录或注册 Tripo 账号)' : ` 1. 在浏览器打开: ${grant.verification_url_complete}(如未登录,先登录或注册)`,
393
- ` 2. 在页面的「Verification code」输入框填入终端码: ${grant.user_code}`,
394
- ' 3. 点击「Authorize」完成授权,回到这个终端等待即可',
395
- ].join('\n'),
396
- en: [
397
- 'Three steps:',
398
- willOpen ? ' 1. your browser will open the authorization page (sign in or sign up first if needed)' : ` 1. open in a browser: ${grant.verification_url_complete} (sign in or sign up first if needed)`,
399
- ` 2. type this one-time code into the "Verification code" field: ${grant.user_code}`,
400
- ' 3. click "Authorize", then come back to this terminal and wait',
401
- ].join('\n'),
402
- }));
312
+ log([
313
+ 'Three steps:',
314
+ willOpen ? ' 1. your browser will open the authorization page (sign in or sign up first if needed)' : ` 1. open in a browser: ${grant.verification_url_complete} (sign in or sign up first if needed)`,
315
+ ` 2. type this one-time code into the "Verification code" field: ${grant.user_code}`,
316
+ ' 3. click "Authorize", then come back to this terminal and wait',
317
+ ].join('\n'));
403
318
  if (willOpen) {
404
319
  openExternal(grant.verification_url_complete);
405
- log(t({ zh: `(浏览器未弹出?手动访问: ${grant.verification_url_complete})`, en: `(browser didn't open? visit: ${grant.verification_url_complete})` }));
320
+ log(`(browser didn't open? visit: ${grant.verification_url_complete})`);
406
321
  }
407
- const spinner = createSpinner(t({
408
- zh: `等待网页授权… 终端码 ${grant.user_code}(Ctrl+C 取消,或改用 tripo login --paste)`,
409
- en: `waiting for approval… code ${grant.user_code} (Ctrl+C to cancel, or use tripo login --paste)`,
410
- }));
322
+ const spinner = createSpinner(`waiting for approval… code ${grant.user_code} (Ctrl+C to cancel, or use tripo login --paste)`);
411
323
  const result = await waitForDeviceApproval(region, grant);
412
324
  if (result.status === 'approved') {
413
- spinner.succeed(t({ zh: '网页授权完成', en: 'authorization approved' }));
325
+ spinner.succeed('authorization approved');
414
326
  return result.apikey;
415
327
  }
416
328
  spinner.stop();
417
329
  if (result.status === 'denied') {
418
- throw new CliError(t({ zh: '授权被拒绝;重新运行 tripo login 再试', en: 'authorization denied — run tripo login to try again' }), EXIT.AUTH);
330
+ throw new CliError('authorization denied — run tripo login to try again', EXIT.AUTH);
419
331
  }
420
- throw new CliError(t({
421
- zh: '授权超时或已过期;重新运行 tripo login,或改用 tripo login --paste 粘贴 Key',
422
- en: 'authorization expired — run tripo login again, or use tripo login --paste',
423
- }), EXIT.AUTH);
332
+ throw new CliError('authorization expired — run tripo login again, or use tripo login --paste', EXIT.AUTH);
424
333
  }
425
334
  function normalizeRegion(value) {
426
335
  if (value === undefined)
@@ -463,26 +372,23 @@ async function keyFromConsole(region, openBrowser) {
463
372
  const keysUrl = `${platformUrlFor(region)}/${REGION_DEFAULTS[region].lang}/keys`;
464
373
  if (openBrowser && shouldOpen()) {
465
374
  openExternal(keysUrl);
466
- log(t({ zh: `已打开控制台 API Keys 页面(未弹出请手动访问):\n ${keysUrl}`, en: `opened the console API Keys page (visit manually if it didn't open):\n ${keysUrl}` }));
375
+ log(`opened the console API Keys page (visit manually if it didn't open):\n ${keysUrl}`);
467
376
  }
468
377
  else {
469
- log(t({ zh: `请访问: ${keysUrl}`, en: `visit: ${keysUrl}` }));
378
+ log(`visit: ${keysUrl}`);
470
379
  }
471
- log(t({
472
- zh: `在网页上登录并创建/复制 Key,回来粘贴(新用户注册可领免费额度;选错站?Ctrl+C 后运行 tripo login --region ${region === 'cn' ? 'ov' : 'cn'})`,
473
- en: `sign in on the page, create/copy a key, then paste it here (new accounts get free credits; wrong site? Ctrl+C and run: tripo login --region ${region === 'cn' ? 'ov' : 'cn'})`,
474
- }));
380
+ log(`sign in on the page, create/copy a key, then paste it here (new accounts get free credits; wrong site? Ctrl+C and run: tripo login --region ${region === 'cn' ? 'ov' : 'cn'})`);
475
381
  const pastePromise = p
476
382
  .password({
477
- message: t({ zh: '粘贴 API Key (tsk_...)', en: 'Paste your API key (tsk_...)' }),
478
- validate: (v) => (v && v.trim().startsWith('tsk_') ? undefined : t({ zh: 'Key 应以 tsk_ 开头', en: 'keys start with tsk_' })),
383
+ message: 'Paste your API key (tsk_...)',
384
+ validate: (v) => (v && v.trim().startsWith('tsk_') ? undefined : 'keys start with tsk_'),
479
385
  })
480
386
  .then((v) => {
481
387
  if (p.isCancel(v))
482
388
  throw new CliError('cancelled', EXIT.FATAL);
483
389
  return v.trim();
484
390
  });
485
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new CliError(t({ zh: '登录超时(5 分钟)', en: 'login timed out (5 minutes)' }), EXIT.AUTH)), LOGIN_TIMEOUT_MS));
391
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new CliError('login timed out (5 minutes)', EXIT.AUTH)), LOGIN_TIMEOUT_MS));
486
392
  try {
487
393
  return await Promise.race([pastePromise, timeoutPromise]);
488
394
  }
@@ -518,10 +424,7 @@ async function validateAndSave(key, selectedRegion, profile) {
518
424
  throw regionDetectionFailure(detected.failures);
519
425
  }
520
426
  if (selectedRegion && selectedRegion !== detected.region) {
521
- warn(t({
522
- zh: `这把 Key 属于${detected.region === 'cn' ? '国内' : '海外'}站,已自动切换 region=${detected.region}(你选的 ${selectedRegion} 不生效)`,
523
- en: `this key belongs to the ${detected.region} region — auto-switched (your choice "${selectedRegion}" was wrong)`,
524
- }));
427
+ warn(`this key belongs to the ${detected.region} region — auto-switched (your choice "${selectedRegion}" was wrong)`);
525
428
  }
526
429
  finishSave(key, detected.region, detected.balance.balance, profile);
527
430
  return { region: detected.region, balance: detected.balance.balance };
@@ -536,27 +439,15 @@ const REGION_HOSTS = { ov: 'openapi.tripo3d.ai', cn: 'openapi.tripo3d.com' };
536
439
  */
537
440
  function regionDetectionFailure(failures) {
538
441
  if (failures.ov === 'rejected' && failures.cn === 'rejected') {
539
- return new AuthError(t({
540
- zh: 'Key 验证失败:国内(openapi.tripo3d.com)和海外(openapi.tripo3d.ai)都不接受这把 Key — 无效或已被删除',
541
- en: 'key validation failed: rejected by both cn (openapi.tripo3d.com) and ov (openapi.tripo3d.ai) — invalid or deleted',
542
- }));
543
- }
544
- const networkHint = t({
545
- zh: '检查本机网络/防火墙;出网需要 HTTP 代理时,设置 HTTPS_PROXY 环境变量后重试;tripo doctor 可自检',
546
- en: 'check this machine\'s network/firewall; if outbound traffic needs an HTTP proxy, set HTTPS_PROXY and retry; "tripo doctor" helps diagnose',
547
- });
442
+ return new AuthError('key validation failed: rejected by both cn (openapi.tripo3d.com) and ov (openapi.tripo3d.ai) — invalid or deleted');
443
+ }
444
+ const networkHint = 'check this machine\'s network/firewall; if outbound traffic needs an HTTP proxy, set HTTPS_PROXY and retry; "tripo doctor" helps diagnose';
548
445
  if (failures.ov === 'unreachable' && failures.cn === 'unreachable') {
549
- return new NetworkError(t({
550
- zh: '无法验证 Key:海外(openapi.tripo3d.ai)和国内(openapi.tripo3d.com)都连接失败 — 这是网络问题,不代表 Key 无效',
551
- en: 'could not validate the key: both ov (openapi.tripo3d.ai) and cn (openapi.tripo3d.com) are unreachable — a network problem, not proof of a bad key',
552
- }), networkHint);
446
+ return new NetworkError('could not validate the key: both ov (openapi.tripo3d.ai) and cn (openapi.tripo3d.com) are unreachable — a network problem, not proof of a bad key', networkHint);
553
447
  }
554
448
  const down = failures.ov === 'unreachable' ? 'ov' : 'cn';
555
449
  const up = down === 'ov' ? 'cn' : 'ov';
556
- return new NetworkError(t({
557
- zh: `Key 验证失败:${up === 'cn' ? '国内' : '海外'}站(${REGION_HOSTS[up]})拒绝了这把 Key,而${down === 'cn' ? '国内' : '海外'}站(${REGION_HOSTS[down]})无法连接 — 如果 Key 属于${down === 'cn' ? '国内' : '海外'}站,请先解决网络问题`,
558
- en: `key validation failed: ${up} (${REGION_HOSTS[up]}) rejected the key and ${down} (${REGION_HOSTS[down]}) is unreachable — if the key belongs to ${down}, fix the network first`,
559
- }), networkHint);
450
+ return new NetworkError(`key validation failed: ${up} (${REGION_HOSTS[up]}) rejected the key and ${down} (${REGION_HOSTS[down]}) is unreachable — if the key belongs to ${down}, fix the network first`, networkHint);
560
451
  }
561
452
  function finishSave(key, region, balance, profile) {
562
453
  // Saving activates the profile: "the account I just logged into is the
@@ -566,21 +457,15 @@ function finishSave(key, region, balance, profile) {
566
457
  const where = profile === DEFAULT_PROFILE ? '~/.tripo/config.json' : `~/.tripo/config.json · profile "${profile}"`;
567
458
  // "mode 0600" is only true where POSIX modes exist; on Windows the file
568
459
  // relies on the user profile's NTFS ACLs, so the claim would be wrong.
569
- const mode = process.platform === 'win32' ? '' : t({ zh: ',权限 0600', en: ', mode 0600' });
570
- success(t({
571
- zh: `Key 验证通过(region=${region}),当前余额 ${balance} 积分(已保存到 ${where}${mode})`,
572
- en: `key verified (region=${region}); balance ${balance} credits (saved to ${where}${mode})`,
573
- }));
460
+ const mode = process.platform === 'win32' ? '' : ', mode 0600';
461
+ success(`key verified (region=${region}); balance ${balance} credits (saved to ${where}${mode})`);
574
462
  if (Number(balance) === 0) {
575
- warn(t({
576
- zh: '余额为 0:新账号可在控制台领取免费额度,或运行 tripo topup 充值',
577
- en: 'balance is 0: claim free credits in the console for new accounts, or run "tripo topup"',
578
- }));
463
+ warn('balance is 0: claim free credits in the console for new accounts, or run "tripo topup"');
579
464
  }
580
465
  }
581
466
  function asAuthError(err) {
582
467
  if (err instanceof CliError && err.exitCode === EXIT.AUTH) {
583
- return new AuthError(t({ zh: 'Key 验证失败:无效或已被删除', en: 'key validation failed: invalid or deleted' }));
468
+ return new AuthError('key validation failed: invalid or deleted');
584
469
  }
585
470
  return err instanceof Error ? err : new Error(String(err));
586
471
  }