tripo-cli 0.1.0 → 0.2.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.
Files changed (64) hide show
  1. package/README.md +19 -3
  2. package/dist/commands/completion.js +1 -1
  3. package/dist/commands/config-cmd.js +32 -17
  4. package/dist/commands/doctor.js +6 -5
  5. package/dist/commands/home.js +4 -2
  6. package/dist/commands/login.js +129 -32
  7. package/dist/config/config.js +103 -14
  8. package/dist/core/client.js +16 -2
  9. package/dist/core/device-auth.js +41 -9
  10. package/dist/program.js +21 -1
  11. package/package.json +2 -2
  12. package/skill/commands/account.md +15 -4
  13. package/dist/src/ai/experts.js +0 -115
  14. package/dist/src/ai/llm.js +0 -88
  15. package/dist/src/ai/plan-card.js +0 -79
  16. package/dist/src/ai/wizard.js +0 -104
  17. package/dist/src/cli.js +0 -7
  18. package/dist/src/commands/account.js +0 -27
  19. package/dist/src/commands/ai.js +0 -113
  20. package/dist/src/commands/batch.js +0 -148
  21. package/dist/src/commands/completion.js +0 -84
  22. package/dist/src/commands/config-cmd.js +0 -84
  23. package/dist/src/commands/docs.js +0 -30
  24. package/dist/src/commands/doctor.js +0 -94
  25. package/dist/src/commands/generate.js +0 -81
  26. package/dist/src/commands/home.js +0 -68
  27. package/dist/src/commands/login.js +0 -225
  28. package/dist/src/commands/make.js +0 -142
  29. package/dist/src/commands/mcp.js +0 -194
  30. package/dist/src/commands/process-groups.js +0 -164
  31. package/dist/src/commands/shared.js +0 -75
  32. package/dist/src/commands/task.js +0 -127
  33. package/dist/src/commands/topup.js +0 -61
  34. package/dist/src/commands/view.js +0 -97
  35. package/dist/src/config/config.js +0 -79
  36. package/dist/src/config/context.js +0 -44
  37. package/dist/src/config/history.js +0 -104
  38. package/dist/src/core/client.js +0 -120
  39. package/dist/src/core/download.js +0 -107
  40. package/dist/src/core/errors.js +0 -64
  41. package/dist/src/core/output.js +0 -72
  42. package/dist/src/core/requests.js +0 -169
  43. package/dist/src/core/task-service.js +0 -161
  44. package/dist/src/core/upload.js +0 -36
  45. package/dist/src/core/viewer-assets.js +0 -24
  46. package/dist/src/core/viewer.js +0 -59
  47. package/dist/src/core/watch.js +0 -41
  48. package/dist/src/i18n.js +0 -19
  49. package/dist/src/knowledge/chains.js +0 -152
  50. package/dist/src/knowledge/error-catalog.js +0 -91
  51. package/dist/src/knowledge/models.js +0 -110
  52. package/dist/src/knowledge/params.js +0 -174
  53. package/dist/src/knowledge/scenarios.js +0 -112
  54. package/dist/src/pipeline/make.js +0 -233
  55. package/dist/src/pipeline/runner.js +0 -73
  56. package/dist/src/pipeline/stdin.js +0 -40
  57. package/dist/src/program.js +0 -147
  58. package/dist/src/types.js +0 -2
  59. package/dist/src/util/input-detect.js +0 -92
  60. package/dist/src/util/notify.js +0 -17
  61. package/dist/src/util/open.js +0 -18
  62. package/dist/src/util/spinner.js +0 -62
  63. package/dist/src/util/term-image.js +0 -28
  64. package/dist/src/version.js +0 -10
@@ -1,113 +0,0 @@
1
- /**
2
- * `tripo ai` — dual mode:
3
- * wizard (default without an LLM): knowledge-base decision-tree Q&A
4
- * conversation (with an LLM configured): producer + specialist agents
5
- * Both produce the same plan card; confirmed plans execute through the same
6
- * deterministic engine as `tripo make`.
7
- */
8
- import * as p from '@clack/prompts';
9
- import { resolveReference } from '../config/history.js';
10
- import { CliError } from '../core/errors.js';
11
- import { isInteractive, isYes, log, warn } from '../core/output.js';
12
- import { t } from '../i18n.js';
13
- import { EXIT } from '../knowledge/error-catalog.js';
14
- import { executeMake, planMake } from '../pipeline/make.js';
15
- import { renderPlanCard } from '../ai/plan-card.js';
16
- import { resolveLlmConfig } from '../ai/llm.js';
17
- import { buildExpertPlan } from '../ai/experts.js';
18
- import { runWizard } from '../ai/wizard.js';
19
- import { emitMakeOutcome } from './make.js';
20
- import { newClient, withTaskOptions } from './shared.js';
21
- export function registerAiCommand(program) {
22
- const cmd = program
23
- .command('ai')
24
- .description('describe what you want in natural language; experts plan it, you confirm, it runs')
25
- .argument('[request...]', 'e.g. "给我的手游做一个低模骑士,要跑步动画"')
26
- .option('--wizard', 'force wizard mode even when an LLM is configured');
27
- withTaskOptions(cmd);
28
- cmd.action(async (request, flags) => {
29
- const text = expandHistoryRefs(request.join(' ').trim());
30
- const llm = resolveLlmConfig();
31
- let card;
32
- let makeOptions;
33
- if (llm && !flags.wizard && text) {
34
- log(t({ zh: `对话模式(${llm.model})…`, en: `conversation mode (${llm.model})…` }));
35
- try {
36
- const plan = await buildExpertPlan(text, isInteractive() ? askUser : undefined);
37
- card = plan.card;
38
- makeOptions = {
39
- inputs: [plan.prompt],
40
- scenario: plan.scenarioId,
41
- chain: plan.chain,
42
- params: plan.params,
43
- name: flags.name,
44
- outDir: flags.out,
45
- notify: flags.notify,
46
- timeoutMs: flags.timeout ? flags.timeout * 1000 : undefined,
47
- };
48
- }
49
- catch (err) {
50
- warn(t({
51
- zh: `LLM 编排失败(${err instanceof Error ? err.message : err}),回落到向导模式`,
52
- en: `LLM planning failed (${err instanceof Error ? err.message : err}); falling back to the wizard`,
53
- }));
54
- const wizard = await runWizard(text);
55
- card = wizard.card;
56
- makeOptions = wizardToOptions(wizard.options, flags);
57
- }
58
- }
59
- else {
60
- if (llm === undefined && text && !isInteractive()) {
61
- throw new CliError(t({
62
- zh: '无 LLM 配置时 tripo ai 需要交互终端;无头环境请用 tripo make --for <场景>(或配置 llm.base_url/llm.model)',
63
- en: 'without an LLM, "tripo ai" needs an interactive terminal; headless callers should use "tripo make --for <scenario>" (or configure llm.base_url/llm.model)',
64
- }), EXIT.USAGE);
65
- }
66
- const wizard = await runWizard(text || undefined);
67
- card = wizard.card;
68
- makeOptions = wizardToOptions(wizard.options, flags);
69
- }
70
- process.stderr.write(renderPlanCard(card) + '\n');
71
- if (!isYes()) {
72
- const go = await p.confirm({ message: t({ zh: '按方案执行?', en: 'Run this plan?' }) });
73
- if (p.isCancel(go) || !go) {
74
- log(t({ zh: '已取消,未消耗积分', en: 'cancelled; no credits spent' }));
75
- return;
76
- }
77
- }
78
- const client = newClient();
79
- const plan = await planMake(client, makeOptions);
80
- const outcome = await executeMake(client, plan, makeOptions);
81
- emitMakeOutcome({ results: [outcome.primary], chainResults: outcome.chainResults, stoppedEarly: outcome.stoppedEarly });
82
- });
83
- }
84
- function wizardToOptions(options, flags) {
85
- return {
86
- inputs: [options.prompt],
87
- scenario: options.scenario,
88
- chain: options.chain,
89
- params: flags.param,
90
- name: flags.name,
91
- outDir: flags.out,
92
- notify: flags.notify,
93
- timeoutMs: flags.timeout ? flags.timeout * 1000 : undefined,
94
- };
95
- }
96
- async function askUser(question) {
97
- const answer = await p.text({ message: question });
98
- if (p.isCancel(answer))
99
- return undefined;
100
- return answer;
101
- }
102
- /** Replace @task-name references in a prompt with their task ids. */
103
- export function expandHistoryRefs(text) {
104
- return text.replace(/@[\w\u4e00-\u9fff-]+/g, (ref) => {
105
- try {
106
- const entry = resolveReference(ref);
107
- return entry ? entry.task_id : ref;
108
- }
109
- catch {
110
- return ref; // ambiguity is reported when the value is actually used
111
- }
112
- });
113
- }
@@ -1,148 +0,0 @@
1
- /**
2
- * `tripo batch run <manifest.yaml>` — bulk production with concurrency,
3
- * retries, and resumable state (skip jobs already succeeded on re-run).
4
- *
5
- * Manifest:
6
- * concurrency: 2
7
- * defaults: { for: game-mobile, then: "texture,convert:fbx", out: ./assets }
8
- * jobs:
9
- * - input: "a sword"
10
- * - input: cat.png
11
- * for: print
12
- * - inputs: [hero-front.png, hero-back.png]
13
- * name: hero
14
- */
15
- import { readFileSync, writeFileSync } from 'node:fs';
16
- import { resolve } from 'node:path';
17
- import { parse as parseYaml } from 'yaml';
18
- import { UsageError } from '../core/errors.js';
19
- import { emitResult, errorLine, log, success } from '../core/output.js';
20
- import { t } from '../i18n.js';
21
- import { parseThenExpression } from '../knowledge/chains.js';
22
- import { executeMake, planMake } from '../pipeline/make.js';
23
- import { newClient } from './shared.js';
24
- export function registerBatchCommand(program) {
25
- const batch = program.command('batch').description('bulk pipelines from a YAML manifest');
26
- batch
27
- .command('run <manifest>')
28
- .description('run all jobs (resumable: finished jobs are skipped on re-run)')
29
- .option('--concurrency <n>', 'parallel jobs (default from manifest or 2)', (v) => Number(v))
30
- .option('--retries <n>', 'retries per failed job', (v) => Number(v), 1)
31
- .option('--fresh', 'ignore previous state and re-run everything')
32
- .action(async (manifestPath, opts) => {
33
- const absPath = resolve(manifestPath);
34
- const manifest = loadManifest(absPath);
35
- const statePath = `${absPath}.state.json`;
36
- const state = opts.fresh ? { jobs: {} } : loadState(statePath);
37
- const jobs = manifest.jobs.map((job, index) => ({
38
- key: job.name ?? `job-${index + 1}`,
39
- spec: { ...manifest.defaults, ...job },
40
- }));
41
- // Duplicate names would corrupt state bookkeeping — fail fast.
42
- const seen = new Set();
43
- for (const { key } of jobs) {
44
- if (seen.has(key))
45
- throw new UsageError(`duplicate job name "${key}" in manifest`);
46
- seen.add(key);
47
- }
48
- const concurrency = Math.max(1, opts.concurrency ?? manifest.concurrency ?? 2);
49
- const queue = jobs.filter(({ key }) => state.jobs[key]?.status !== 'success');
50
- const skipped = jobs.length - queue.length;
51
- if (skipped > 0) {
52
- log(t({ zh: `跳过 ${skipped} 个已完成任务(断点续跑)`, en: `skipping ${skipped} already-finished job(s) (resume)` }));
53
- }
54
- log(t({ zh: `批量执行 ${queue.length} 个任务,并发 ${concurrency}`, en: `running ${queue.length} job(s), concurrency ${concurrency}` }));
55
- const client = newClient();
56
- let cursor = 0;
57
- const runNext = async () => {
58
- for (;;) {
59
- const item = queue[cursor++];
60
- if (!item)
61
- return;
62
- const jobState = state.jobs[item.key] ?? { status: 'pending', attempts: 0 };
63
- state.jobs[item.key] = jobState;
64
- const maxAttempts = 1 + opts.retries;
65
- for (let attempt = jobState.attempts + 1; attempt <= maxAttempts; attempt++) {
66
- jobState.attempts = attempt;
67
- try {
68
- const outcome = await runJob(client, item.key, item.spec);
69
- jobState.status = 'success';
70
- jobState.task_id = outcome.taskId;
71
- jobState.output_dir = outcome.outputDir;
72
- delete jobState.error;
73
- success(`[${item.key}] done${outcome.outputDir ? ` → ${outcome.outputDir}` : ''}`);
74
- break;
75
- }
76
- catch (err) {
77
- const message = err instanceof Error ? err.message : String(err);
78
- jobState.status = 'failed';
79
- jobState.error = message;
80
- errorLine(`[${item.key}] attempt ${attempt}/${maxAttempts} failed: ${message}`);
81
- }
82
- finally {
83
- saveState(statePath, state);
84
- }
85
- }
86
- }
87
- };
88
- await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, runNext));
89
- const summary = {
90
- total: jobs.length,
91
- success: Object.values(state.jobs).filter((j) => j.status === 'success').length,
92
- failed: Object.values(state.jobs).filter((j) => j.status === 'failed').length,
93
- state_file: statePath,
94
- jobs: state.jobs,
95
- };
96
- const failed = summary.failed;
97
- emitResult(summary, t({
98
- zh: `批量完成: 成功 ${summary.success}/${summary.total}${failed ? `,失败 ${failed}(重跑同一命令续跑)` : ''}`,
99
- en: `batch finished: ${summary.success}/${summary.total} succeeded${failed ? `, ${failed} failed (re-run the same command to resume)` : ''}`,
100
- }));
101
- if (failed > 0)
102
- process.exitCode = 6;
103
- });
104
- }
105
- async function runJob(client, key, spec) {
106
- const inputs = spec.inputs ?? (spec.input !== undefined ? [spec.input] : []);
107
- if (inputs.length === 0)
108
- throw new UsageError(`job "${key}" has no input`);
109
- const chain = spec.then ? parseThenExpression(spec.then) : undefined;
110
- const options = {
111
- inputs,
112
- scenario: spec.for,
113
- chain,
114
- model: spec.model,
115
- params: spec.params ?? {},
116
- name: spec.name ?? key,
117
- outDir: spec.out,
118
- };
119
- const plan = await planMake(client, options);
120
- const outcome = await executeMake(client, plan, options);
121
- const finalResult = outcome.chainResults[outcome.chainResults.length - 1] ?? outcome.primary;
122
- return { taskId: finalResult.task_id, outputDir: finalResult.output_dir ?? outcome.primary.output_dir };
123
- }
124
- function loadManifest(path) {
125
- let raw;
126
- try {
127
- raw = readFileSync(path, 'utf8');
128
- }
129
- catch {
130
- throw new UsageError(`manifest not found: ${path}`);
131
- }
132
- const doc = parseYaml(raw);
133
- if (!doc || !Array.isArray(doc.jobs) || doc.jobs.length === 0) {
134
- throw new UsageError('manifest must contain a non-empty "jobs" list');
135
- }
136
- return doc;
137
- }
138
- function loadState(path) {
139
- try {
140
- return JSON.parse(readFileSync(path, 'utf8'));
141
- }
142
- catch {
143
- return { jobs: {} };
144
- }
145
- }
146
- function saveState(path, state) {
147
- writeFileSync(path, JSON.stringify(state, null, 2) + '\n');
148
- }
@@ -1,84 +0,0 @@
1
- /** `tripo completion <shell>` — static shell completion scripts. */
2
- import { UsageError } from '../core/errors.js';
3
- const COMMANDS = [
4
- 'login', 'logout', 'whoami', 'balance', 'usage', 'topup',
5
- 'make', 'view', 'redo', 'ai',
6
- 'generate', 'model', 'anim', 'mesh', 'task', 'files', 'batch', 'history',
7
- 'config', 'doctor', 'docs', 'completion', 'mcp',
8
- ];
9
- const SUBS = {
10
- generate: ['text-to-model', 'image-to-model', 'multiview-to-model', 'text-to-image', 'image-to-image', 'image-to-multiview', 'edit-multiview', 'image-to-splat'],
11
- model: ['refine', 'texture', 'stylize', 'convert', 'import'],
12
- anim: ['check', 'rig', 'retarget'],
13
- mesh: ['segment', 'complete', 'decimate', 'smartsegment'],
14
- task: ['get', 'list', 'watch'],
15
- files: ['upload'],
16
- batch: ['run'],
17
- config: ['list', 'set', 'unset', 'context'],
18
- };
19
- export function registerCompletionCommand(program) {
20
- program
21
- .command('completion <shell>')
22
- .description('print a completion script (bash|zsh|fish); e.g. tripo completion zsh >> ~/.zshrc')
23
- .action((shell) => {
24
- switch (shell) {
25
- case 'bash':
26
- process.stdout.write(bashScript());
27
- break;
28
- case 'zsh':
29
- process.stdout.write(zshScript());
30
- break;
31
- case 'fish':
32
- process.stdout.write(fishScript());
33
- break;
34
- default:
35
- throw new UsageError('supported shells: bash, zsh, fish');
36
- }
37
- });
38
- }
39
- function bashScript() {
40
- const cases = Object.entries(SUBS)
41
- .map(([cmd, subs]) => ` ${cmd}) COMPREPLY=($(compgen -W "${subs.join(' ')}" -- "$cur"));;`)
42
- .join('\n');
43
- return `# tripo bash completion
44
- _tripo_completions() {
45
- local cur prev
46
- cur="\${COMP_WORDS[COMP_CWORD]}"
47
- prev="\${COMP_WORDS[1]}"
48
- if [ "$COMP_CWORD" -eq 1 ]; then
49
- COMPREPLY=($(compgen -W "${COMMANDS.join(' ')}" -- "$cur"))
50
- return
51
- fi
52
- case "$prev" in
53
- ${cases}
54
- esac
55
- }
56
- complete -F _tripo_completions tripo
57
- `;
58
- }
59
- function zshScript() {
60
- return `# tripo zsh completion
61
- autoload -Uz compinit && compinit
62
- _tripo() {
63
- local -a cmds
64
- cmds=(${COMMANDS.join(' ')})
65
- if (( CURRENT == 2 )); then
66
- compadd -a cmds
67
- else
68
- case $words[2] in
69
- ${Object.entries(SUBS)
70
- .map(([cmd, subs]) => ` ${cmd}) compadd ${subs.join(' ')};;`)
71
- .join('\n')}
72
- esac
73
- fi
74
- }
75
- compdef _tripo tripo
76
- `;
77
- }
78
- function fishScript() {
79
- const lines = [
80
- `complete -c tripo -f -n '__fish_use_subcommand' -a '${COMMANDS.join(' ')}'`,
81
- ...Object.entries(SUBS).map(([cmd, subs]) => `complete -c tripo -f -n '__fish_seen_subcommand_from ${cmd}' -a '${subs.join(' ')}'`),
82
- ];
83
- return lines.join('\n') + '\n';
84
- }
@@ -1,84 +0,0 @@
1
- /** `tripo config` — global config + directory context management. */
2
- import { loadConfig, maskKey, saveConfig } from '../config/config.js';
3
- import { findContextFile, loadContext, updateContext } from '../config/context.js';
4
- import { UsageError } from '../core/errors.js';
5
- import { emitResult, success } from '../core/output.js';
6
- import { coerceValue } from './shared.js';
7
- const SETTABLE = new Set([
8
- 'region',
9
- 'api_base_url',
10
- 'platform_base_url',
11
- 'language',
12
- 'default_output_dir',
13
- 'llm.base_url',
14
- 'llm.api_key',
15
- 'llm.model',
16
- ]);
17
- export function registerConfigCommands(program) {
18
- const config = program.command('config').description('global config (~/.tripo) and directory context (.tripo)');
19
- config
20
- .command('list')
21
- .description('show effective config (key masked)')
22
- .action(() => {
23
- const cfg = loadConfig();
24
- const shown = {
25
- ...cfg,
26
- api_key: cfg.api_key ? maskKey(cfg.api_key) : undefined,
27
- llm: cfg.llm ? { ...cfg.llm, api_key: cfg.llm.api_key ? maskKey(cfg.llm.api_key) : undefined } : undefined,
28
- };
29
- const context = loadContext();
30
- emitResult({ config: shown, context, context_file: findContextFile() }, JSON.stringify({ config: shown, context }, null, 2));
31
- });
32
- config
33
- .command('set <key> <value>')
34
- .description(`set a config value (${[...SETTABLE].join(', ')})`)
35
- .action((key, value) => {
36
- if (!SETTABLE.has(key)) {
37
- throw new UsageError(`unknown config key "${key}" (valid: ${[...SETTABLE].join(', ')})`);
38
- }
39
- const cfg = loadConfig();
40
- applyPath(cfg, key.split('.'), coerceValue(value));
41
- saveConfig(cfg);
42
- success(`${key} = ${key.includes('api_key') ? maskKey(value) : value}`);
43
- });
44
- config
45
- .command('unset <key>')
46
- .description('remove a config value')
47
- .action((key) => {
48
- const cfg = loadConfig();
49
- const parts = key.split('.');
50
- const parent = parts.slice(0, -1).reduce((acc, part) => (acc && typeof acc[part] === 'object' ? acc[part] : undefined), cfg);
51
- if (parent)
52
- delete parent[parts[parts.length - 1]];
53
- saveConfig(cfg);
54
- success(`${key} removed`);
55
- });
56
- config
57
- .command('context [entries...]')
58
- .description('set directory context: tripo config context default_scenario=game-mobile output_dir=./assets')
59
- .action((entries) => {
60
- if (entries.length === 0) {
61
- const ctx = loadContext();
62
- emitResult({ context: ctx, file: findContextFile() }, JSON.stringify(ctx, null, 2));
63
- return;
64
- }
65
- const patch = {};
66
- for (const entry of entries) {
67
- const eq = entry.indexOf('=');
68
- if (eq <= 0)
69
- throw new UsageError(`context entries are key=value (got "${entry}")`);
70
- patch[entry.slice(0, eq)] = coerceValue(entry.slice(eq + 1));
71
- }
72
- updateContext(patch);
73
- success(`context updated: ${Object.keys(patch).join(', ')}`);
74
- });
75
- }
76
- function applyPath(obj, path, value) {
77
- let node = obj;
78
- for (const part of path.slice(0, -1)) {
79
- if (typeof node[part] !== 'object' || node[part] === null)
80
- node[part] = {};
81
- node = node[part];
82
- }
83
- node[path[path.length - 1]] = value;
84
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * `tripo docs` — print bundled documentation. `--llm` prints the skill map
3
- * designed for AI coding agents (the npm package ships skill/ alongside dist/).
4
- */
5
- import { existsSync, readFileSync } from 'node:fs';
6
- import { UsageError } from '../core/errors.js';
7
- export function skillRoot() {
8
- return new URL('../../skill/', import.meta.url);
9
- }
10
- export function readSkillFile(relative) {
11
- const url = new URL(relative, skillRoot());
12
- if (!existsSync(url))
13
- throw new UsageError(`documentation file not found: ${relative}`);
14
- return readFileSync(url, 'utf8');
15
- }
16
- export function registerDocsCommand(program) {
17
- program
18
- .command('docs')
19
- .description('print bundled docs (default: the LLM/agent skill map)')
20
- .option('--llm', 'print the agent-facing SKILL.md (default)')
21
- .option('--topic <name>', 'print a specific doc: commands/<cmd> or examples/<scenario> or common-errors')
22
- .action((opts) => {
23
- if (opts.topic) {
24
- const file = opts.topic.endsWith('.md') ? opts.topic : `${opts.topic}.md`;
25
- process.stdout.write(readSkillFile(file));
26
- return;
27
- }
28
- process.stdout.write(readSkillFile('SKILL.md'));
29
- });
30
- }
@@ -1,94 +0,0 @@
1
- /** `tripo doctor` — environment/config/network self-diagnosis. */
2
- import { statSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import pc from 'picocolors';
5
- import { configFileExists, loadConfig, resolveApiBaseUrl, resolveApiKey, tripoHome } from '../config/config.js';
6
- import { TripoClient } from '../core/client.js';
7
- import { emitResult } from '../core/output.js';
8
- import { resolveLlmConfig } from '../ai/llm.js';
9
- import { terminalSupportsInlineImages } from '../util/term-image.js';
10
- export function registerDoctorCommand(program) {
11
- program
12
- .command('doctor')
13
- .description('diagnose environment, key, network, and terminal capabilities')
14
- .action(async () => {
15
- const checks = [];
16
- const nodeMajor = Number(process.versions.node.split('.')[0]);
17
- checks.push({
18
- name: 'node',
19
- ok: nodeMajor >= 20,
20
- detail: `v${process.versions.node} (need >= 20)`,
21
- critical: true,
22
- });
23
- const key = resolveApiKey();
24
- checks.push({
25
- name: 'api key',
26
- ok: Boolean(key),
27
- detail: key
28
- ? process.env.TRIPO_API_KEY
29
- ? 'from TRIPO_API_KEY env'
30
- : 'from ~/.tripo/config.json'
31
- : 'missing — run "tripo login"',
32
- critical: true,
33
- });
34
- if (configFileExists()) {
35
- try {
36
- const mode = statSync(join(tripoHome(), 'config.json')).mode & 0o777;
37
- checks.push({
38
- name: 'config permissions',
39
- ok: mode === 0o600,
40
- detail: `0${mode.toString(8)} (expect 0600)`,
41
- critical: false,
42
- });
43
- }
44
- catch { }
45
- }
46
- const baseUrl = resolveApiBaseUrl();
47
- if (key) {
48
- try {
49
- const client = new TripoClient();
50
- const balance = await client.getBalance();
51
- checks.push({ name: 'api reachability', ok: true, detail: `${baseUrl} · balance ${balance.balance}`, critical: true });
52
- checks.push({
53
- name: 'balance',
54
- ok: Number(balance.balance) > 0,
55
- detail: `${balance.balance} credits (${balance.frozen} frozen)${Number(balance.balance) <= 0 ? ' — run "tripo topup"' : ''}`,
56
- critical: false,
57
- });
58
- }
59
- catch (err) {
60
- checks.push({
61
- name: 'api reachability',
62
- ok: false,
63
- detail: `${baseUrl}: ${err instanceof Error ? err.message : String(err)}`,
64
- critical: true,
65
- });
66
- }
67
- }
68
- else {
69
- checks.push({ name: 'api reachability', ok: false, detail: `skipped (no key) · would use ${baseUrl}`, critical: false });
70
- }
71
- const llm = resolveLlmConfig();
72
- checks.push({
73
- name: 'llm (tripo ai conversation mode)',
74
- ok: true,
75
- detail: llm ? `${llm.model} @ ${llm.base_url}` : 'not configured — wizard mode only (this is fine)',
76
- critical: false,
77
- });
78
- checks.push({
79
- name: 'terminal',
80
- ok: true,
81
- detail: `${process.stdout.isTTY ? 'interactive TTY' : 'non-TTY (headless mode)'}${terminalSupportsInlineImages() ? ' · inline images supported' : ''}`,
82
- critical: false,
83
- });
84
- const language = loadConfig().language ?? 'auto';
85
- checks.push({ name: 'language', ok: true, detail: language, critical: false });
86
- const failedCritical = checks.some((c) => c.critical && !c.ok);
87
- const human = checks
88
- .map((c) => `${c.ok ? pc.green('✓') : c.critical ? pc.red('✗') : pc.yellow('!')} ${c.name.padEnd(34)} ${c.detail}`)
89
- .join('\n');
90
- emitResult({ ok: !failedCritical, checks }, human);
91
- if (failedCritical)
92
- process.exitCode = 1;
93
- });
94
- }
@@ -1,81 +0,0 @@
1
- /** `tripo generate <endpoint>` — deterministic 1:1 mapping to the 8 generation endpoints. */
2
- import { UsageError } from '../core/errors.js';
3
- import { warn } from '../core/output.js';
4
- import { buildEditMultiview, buildImageToImage, buildImageToModel, buildImageToMultiview, buildImageToSplat, buildMultiviewToModel, buildTextToImage, buildTextToModel, } from '../core/requests.js';
5
- import { resolveInputValue, runTask } from '../core/task-service.js';
6
- import { chooseModel } from '../knowledge/models.js';
7
- import { assignViews } from '../pipeline/make.js';
8
- import { newClient, printTaskResult, withTaskOptions } from './shared.js';
9
- export function registerGenerateCommands(program) {
10
- const generate = program.command('generate').description('deterministic generation endpoints (text/image/multiview → model/image/splat)');
11
- const add = (name, description, inputArity, build) => {
12
- const cmd = generate
13
- .command(`${name} ${inputArity === 'prompt' ? '<prompt...>' : inputArity === 'single' ? '<input>' : '<inputs...>'}`)
14
- .description(description)
15
- .option('--model <model>', 'model version override');
16
- if (inputArity !== 'prompt')
17
- cmd.option('--prompt <text>', 'prompt for prompt-guided endpoints');
18
- withTaskOptions(cmd);
19
- cmd.action(async (inputs, flags) => {
20
- const list = Array.isArray(inputs) ? inputs : [inputs];
21
- const client = newClient();
22
- const built = await build(client, list, flags);
23
- for (const w of built.warnings)
24
- warn(w);
25
- const result = await runTask({
26
- client,
27
- request: built.request,
28
- taskType: built.taskType,
29
- name: flags.name ?? (inputArity === 'prompt' ? list.join(' ') : list[0]),
30
- outDir: flags.out,
31
- noWait: flags.wait === false,
32
- noDownload: flags.download === false,
33
- notify: flags.notify,
34
- timeoutMs: flags.timeout ? flags.timeout * 1000 : undefined,
35
- });
36
- printTaskResult(result);
37
- });
38
- };
39
- add('text-to-model', 'text → 3D model', 'prompt', async (_c, inputs, flags) => {
40
- const prompt = inputs.join(' ');
41
- const { model } = chooseModel({ explicit: flags.model, faceLimit: numberParam(flags, 'face_limit') });
42
- return buildTextToModel(prompt, model, flags.param);
43
- });
44
- add('image-to-model', 'single image → 3D model', 'single', async (client, inputs, flags) => {
45
- const { value } = await resolveInputValue(client, inputs[0]);
46
- const { model } = chooseModel({ explicit: flags.model, faceLimit: numberParam(flags, 'face_limit') });
47
- return buildImageToModel(value, model, flags.param);
48
- });
49
- add('multiview-to-model', '2–4 view images → 3D model', 'multi', async (client, inputs, flags) => {
50
- const views = await assignViews(client, inputs);
51
- const { model } = chooseModel({ explicit: flags.model, faceLimit: numberParam(flags, 'face_limit') });
52
- return buildMultiviewToModel(views, model, flags.param);
53
- });
54
- add('text-to-image', 'text → image (concept art / t_pose templates)', 'prompt', async (_c, inputs, flags) => buildTextToImage(inputs.join(' '), { ...(flags.model ? { model: flags.model } : {}), ...flags.param }));
55
- add('image-to-image', 'image + prompt → edited image', 'single', async (client, inputs, flags) => {
56
- const { value } = await resolveInputValue(client, inputs[0]);
57
- const params = { ...(flags.model ? { model: flags.model } : {}), ...flags.param };
58
- if (flags.prompt)
59
- params.prompt = flags.prompt;
60
- if (!params.prompt && !params.template) {
61
- throw new UsageError('image-to-image needs --prompt or --param template=...');
62
- }
63
- return buildImageToImage(value, params);
64
- });
65
- add('image-to-multiview', 'single image → 4-view sheet', 'single', async (client, inputs, flags) => {
66
- const { value } = await resolveInputValue(client, inputs[0]);
67
- return buildImageToMultiview(value, flags.param);
68
- });
69
- add('edit-multiview', 'edit views of a multiview task', 'single', async (client, inputs, flags) => {
70
- const { value } = await resolveInputValue(client, inputs[0]);
71
- return buildEditMultiview(value, flags.param);
72
- });
73
- add('image-to-splat', 'image → gaussian splat', 'single', async (client, inputs, flags) => {
74
- const { value } = await resolveInputValue(client, inputs[0]);
75
- return buildImageToSplat(value, flags.param);
76
- });
77
- }
78
- function numberParam(flags, key) {
79
- const v = flags.param[key];
80
- return typeof v === 'number' ? v : undefined;
81
- }