tripo-cli 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +9 -7
- package/dist/ai/experts.js +25 -19
- package/dist/ai/plan-card.js +71 -33
- package/dist/ai/wizard.js +13 -17
- package/dist/commands/account.js +2 -6
- package/dist/commands/ai.js +18 -16
- package/dist/commands/batch.js +58 -18
- package/dist/commands/config-cmd.js +1 -1
- package/dist/commands/doctor.js +1 -3
- package/dist/commands/generate.js +35 -4
- package/dist/commands/home.js +12 -13
- package/dist/commands/login.js +72 -187
- package/dist/commands/make.js +47 -25
- package/dist/commands/mcp.js +2 -1
- package/dist/commands/process-groups.js +3 -4
- package/dist/commands/shared.js +9 -14
- package/dist/commands/task.js +10 -11
- package/dist/commands/topup.js +6 -10
- package/dist/commands/update.js +6 -10
- package/dist/commands/view.js +3 -4
- package/dist/config/config.js +1 -1
- package/dist/core/client.js +4 -13
- package/dist/core/errors.js +16 -11
- package/dist/core/output.js +0 -4
- package/dist/core/requests.js +61 -17
- package/dist/core/task-service.js +19 -20
- package/dist/knowledge/chains.js +12 -12
- package/dist/knowledge/error-catalog.js +32 -32
- package/dist/knowledge/models.js +119 -20
- package/dist/knowledge/params.js +96 -8
- package/dist/knowledge/scenarios.js +32 -55
- package/dist/pipeline/make.js +36 -28
- package/dist/pipeline/runner.js +7 -12
- package/dist/pipeline/stdin.js +40 -3
- package/dist/program.js +3 -10
- package/package.json +3 -2
- package/skill/SKILL.md +34 -8
- package/skill/commands/account.md +1 -1
- package/skill/commands/batch.md +13 -3
- package/skill/commands/generate.md +35 -10
- package/skill/commands/make.md +39 -13
- package/skill/commands/process.md +2 -1
- package/skill/commands/task.md +2 -2
- package/skill/commands/view.md +1 -1
- package/skill/common-errors.md +6 -3
- package/skill/examples/animation.md +3 -1
- package/skill/examples/ar-web.md +6 -4
- package/skill/examples/game-asset.md +17 -6
- package/skill/examples/pipes.md +2 -2
- package/skill/examples/print.md +7 -3
- package/dist/i18n.js +0 -19
package/dist/commands/login.js
CHANGED
|
@@ -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(
|
|
42
|
+
log(`no API key stored locally for profile "${name}"`);
|
|
44
43
|
}
|
|
45
44
|
else {
|
|
46
|
-
success(
|
|
45
|
+
success(`local API key removed (profile "${name}")`);
|
|
47
46
|
const rest = listProfiles();
|
|
48
47
|
if (rest.length > 0) {
|
|
49
|
-
log(
|
|
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(
|
|
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
|
-
|
|
70
|
+
`balance ${balance.balance} credits (${balance.frozen} frozen)`,
|
|
78
71
|
];
|
|
79
72
|
if (others.length > 0) {
|
|
80
|
-
lines.push(
|
|
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(
|
|
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(
|
|
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:
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
193
|
-
{ value: 'cn', label:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
},
|
|
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(
|
|
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(
|
|
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 },
|
|
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
|
-
?
|
|
342
|
-
:
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
310
|
+
p.note(grant.user_code, 'Your one-time code');
|
|
388
311
|
const willOpen = shouldOpen();
|
|
389
|
-
log(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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(
|
|
320
|
+
log(`(browser didn't open? visit: ${grant.verification_url_complete})`);
|
|
406
321
|
}
|
|
407
|
-
const spinner = createSpinner(
|
|
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(
|
|
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(
|
|
330
|
+
throw new CliError('authorization denied — run tripo login to try again', EXIT.AUTH);
|
|
419
331
|
}
|
|
420
|
-
throw new CliError(
|
|
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(
|
|
375
|
+
log(`opened the console API Keys page (visit manually if it didn't open):\n ${keysUrl}`);
|
|
467
376
|
}
|
|
468
377
|
else {
|
|
469
|
-
log(
|
|
378
|
+
log(`visit: ${keysUrl}`);
|
|
470
379
|
}
|
|
471
|
-
log(
|
|
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:
|
|
478
|
-
validate: (v) => (v && v.trim().startsWith('tsk_') ? undefined :
|
|
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(
|
|
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(
|
|
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(
|
|
540
|
-
|
|
541
|
-
|
|
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(
|
|
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(
|
|
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' ? '' :
|
|
570
|
-
success(
|
|
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(
|
|
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(
|
|
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
|
}
|