@teinai/cli 1.0.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 (2) hide show
  1. package/index.js +1943 -0
  2. package/package.json +24 -0
package/index.js ADDED
@@ -0,0 +1,1943 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/colors.ts
27
+ var isColorSupported = !process.env.NO_COLOR && (process.stdout.isTTY || process.env.FORCE_COLOR === "1" || process.env.TERM !== "dumb");
28
+ function wrap(code, close) {
29
+ return (str) => isColorSupported ? `\x1B[${code}m${str}\x1B[${close}m` : String(str);
30
+ }
31
+ var c = {
32
+ bold: wrap(1, 22),
33
+ dim: wrap(2, 22),
34
+ italic: wrap(3, 23),
35
+ underline: wrap(4, 24),
36
+ inverse: wrap(7, 27),
37
+ black: wrap(30, 39),
38
+ red: wrap(31, 39),
39
+ green: wrap(32, 39),
40
+ yellow: wrap(33, 39),
41
+ blue: wrap(34, 39),
42
+ magenta: wrap(35, 39),
43
+ cyan: wrap(36, 39),
44
+ white: wrap(37, 39),
45
+ gray: wrap(90, 39),
46
+ bgBlue: wrap(44, 49),
47
+ bgMagenta: wrap(45, 49),
48
+ bgCyan: wrap(46, 49),
49
+ bgGreen: wrap(42, 49)
50
+ };
51
+ var Spinner = class {
52
+ constructor(initialText = "") {
53
+ this.frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
54
+ this.current = 0;
55
+ this.timer = null;
56
+ this.text = initialText;
57
+ }
58
+ start(text) {
59
+ if (text) this.text = text;
60
+ if (!process.stdout.isTTY) {
61
+ process.stdout.write(`[i] ${this.text}...
62
+ `);
63
+ return this;
64
+ }
65
+ this.stop();
66
+ this.timer = setInterval(() => {
67
+ const frame = c.cyan(this.frames[this.current]);
68
+ process.stdout.write(`\r${frame} ${this.text} `);
69
+ this.current = (this.current + 1) % this.frames.length;
70
+ }, 80);
71
+ return this;
72
+ }
73
+ update(text) {
74
+ this.text = text;
75
+ }
76
+ succeed(text) {
77
+ this.stop();
78
+ const msg = text || this.text;
79
+ if (process.stdout.isTTY) {
80
+ process.stdout.write(`\r${c.green("\u2714")} ${msg}
81
+ `);
82
+ } else {
83
+ process.stdout.write(`[\u2714] ${msg}
84
+ `);
85
+ }
86
+ }
87
+ fail(text) {
88
+ this.stop();
89
+ const msg = text || this.text;
90
+ if (process.stdout.isTTY) {
91
+ process.stdout.write(`\r${c.red("\u2716")} ${msg}
92
+ `);
93
+ } else {
94
+ process.stdout.write(`[\u2716] ${msg}
95
+ `);
96
+ }
97
+ }
98
+ stop() {
99
+ if (this.timer) {
100
+ clearInterval(this.timer);
101
+ this.timer = null;
102
+ if (process.stdout.isTTY) {
103
+ process.stdout.write("\r\x1B[K");
104
+ }
105
+ }
106
+ }
107
+ };
108
+
109
+ // src/cli/commands/run.ts
110
+ var import_node_path3 = __toESM(require("node:path"));
111
+
112
+ // src/cli/api.ts
113
+ var import_node_fs = __toESM(require("node:fs"));
114
+ var import_node_path = __toESM(require("node:path"));
115
+ var import_promises = require("node:stream/promises");
116
+ var TeinApiClient = class {
117
+ constructor(apiKey, baseUrl) {
118
+ this.apiKey = apiKey;
119
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
120
+ }
121
+ getHeaders() {
122
+ return {
123
+ "Content-Type": "application/json",
124
+ Authorization: `Bearer ${this.apiKey}`,
125
+ "User-Agent": "Tein-CLI/1.0.0"
126
+ };
127
+ }
128
+ async runJob(model, parameters) {
129
+ const url = `${this.baseUrl}/v1/run`;
130
+ const res = await fetch(url, {
131
+ method: "POST",
132
+ headers: this.getHeaders(),
133
+ body: JSON.stringify({ model, parameters })
134
+ });
135
+ if (!res.ok) {
136
+ let errMsg = `HTTP ${res.status} ${res.statusText}`;
137
+ try {
138
+ const body = await res.json();
139
+ errMsg = body.error || body.message || errMsg;
140
+ } catch {
141
+ }
142
+ throw new Error(errMsg);
143
+ }
144
+ return await res.json();
145
+ }
146
+ async queueJob(model, parameters) {
147
+ const url = `${this.baseUrl}/v1/queue`;
148
+ const res = await fetch(url, {
149
+ method: "POST",
150
+ headers: this.getHeaders(),
151
+ body: JSON.stringify({ model, parameters })
152
+ });
153
+ if (!res.ok) {
154
+ let errMsg = `HTTP ${res.status} ${res.statusText}`;
155
+ try {
156
+ const body = await res.json();
157
+ errMsg = body.error || body.message || errMsg;
158
+ } catch {
159
+ }
160
+ throw new Error(errMsg);
161
+ }
162
+ return await res.json();
163
+ }
164
+ async getStatus(requestId) {
165
+ const url = `${this.baseUrl}/v1/status/${requestId}`;
166
+ const res = await fetch(url, {
167
+ method: "GET",
168
+ headers: this.getHeaders()
169
+ });
170
+ if (!res.ok) {
171
+ let errMsg = `HTTP ${res.status} ${res.statusText}`;
172
+ try {
173
+ const body = await res.json();
174
+ errMsg = body.error || body.message || errMsg;
175
+ } catch {
176
+ }
177
+ throw new Error(errMsg);
178
+ }
179
+ return await res.json();
180
+ }
181
+ async downloadOutput(url, destinationPath) {
182
+ const res = await fetch(url);
183
+ if (!res.ok) {
184
+ throw new Error(`Failed to download output from ${url}: HTTP ${res.status}`);
185
+ }
186
+ const resolvedDir = import_node_path.default.dirname(destinationPath);
187
+ if (!import_node_fs.default.existsSync(resolvedDir)) {
188
+ import_node_fs.default.mkdirSync(resolvedDir, { recursive: true });
189
+ }
190
+ const fileStream = import_node_fs.default.createWriteStream(destinationPath);
191
+ await (0, import_promises.pipeline)(res.body, fileStream);
192
+ return destinationPath;
193
+ }
194
+ };
195
+
196
+ // src/cli/config.ts
197
+ var import_node_fs2 = __toESM(require("node:fs"));
198
+ var import_node_os = __toESM(require("node:os"));
199
+ var import_node_path2 = __toESM(require("node:path"));
200
+ var CONFIG_DIR = import_node_path2.default.join(import_node_os.default.homedir(), ".tein");
201
+ var CONFIG_FILE = import_node_path2.default.join(CONFIG_DIR, "config.json");
202
+ function getStoredConfig() {
203
+ try {
204
+ if (import_node_fs2.default.existsSync(CONFIG_FILE)) {
205
+ const raw = import_node_fs2.default.readFileSync(CONFIG_FILE, "utf-8");
206
+ return JSON.parse(raw);
207
+ }
208
+ } catch {
209
+ }
210
+ return {};
211
+ }
212
+ function saveStoredConfig(config) {
213
+ try {
214
+ if (!import_node_fs2.default.existsSync(CONFIG_DIR)) {
215
+ import_node_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
216
+ }
217
+ const current = getStoredConfig();
218
+ const updated = { ...current, ...config };
219
+ import_node_fs2.default.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), "utf-8");
220
+ } catch (err) {
221
+ throw new Error(`Failed to save config to ${CONFIG_FILE}: ${err}`);
222
+ }
223
+ }
224
+ function resolveApiKey(cliKey) {
225
+ if (cliKey && cliKey.trim()) {
226
+ return cliKey.trim();
227
+ }
228
+ const envKey = process.env.TEIN_API_KEY || process.env.TEIN_KEY;
229
+ if (envKey && envKey.trim()) {
230
+ return envKey.trim();
231
+ }
232
+ const stored = getStoredConfig();
233
+ if (stored.apiKey && stored.apiKey.trim()) {
234
+ return stored.apiKey.trim();
235
+ }
236
+ return "";
237
+ }
238
+ function resolveApiUrl(cliUrl) {
239
+ if (cliUrl && cliUrl.trim()) {
240
+ return cliUrl.trim().replace(/\/+$/, "");
241
+ }
242
+ const envUrl = process.env.TEIN_API_URL;
243
+ if (envUrl && envUrl.trim()) {
244
+ return envUrl.trim().replace(/\/+$/, "");
245
+ }
246
+ const stored = getStoredConfig();
247
+ if (stored.apiUrl && stored.apiUrl.trim()) {
248
+ return stored.apiUrl.trim().replace(/\/+$/, "");
249
+ }
250
+ return "https://api.tein.ai";
251
+ }
252
+ function getConfigPath() {
253
+ return CONFIG_FILE;
254
+ }
255
+
256
+ // src/mcp/registry.ts
257
+ var MCP_MODELS = {
258
+ "seedance-2-5": {
259
+ slug: "seedance-2-5",
260
+ name: "Seedance 2.5",
261
+ category: "Video",
262
+ kind: "Text-to-Video",
263
+ vendor: "ByteDance",
264
+ description: "ByteDance's flagship video model \u2014 cinematic motion, native audio, director-level camera control.",
265
+ priceFormatted: "$0.1284/sec",
266
+ unit: "/sec",
267
+ primaryToolName: "generate_video",
268
+ parameters: [
269
+ {
270
+ name: "prompt",
271
+ type: "string",
272
+ required: true,
273
+ description: "Text prompt describing the desired video scene, actions, camera angles, lighting, and style."
274
+ },
275
+ {
276
+ name: "mode",
277
+ type: "select",
278
+ default: "text_to_video",
279
+ options: [
280
+ "text_to_video",
281
+ "first_n_last_frames",
282
+ "multi_reference",
283
+ "edit",
284
+ "extend",
285
+ "multi_frame",
286
+ "lipsyncing",
287
+ "voice_clone",
288
+ "ugc"
289
+ ],
290
+ description: "Generation mode for Seedance 2.5."
291
+ },
292
+ {
293
+ name: "duration",
294
+ type: "slider",
295
+ default: 5,
296
+ min: 4,
297
+ max: 30,
298
+ description: "Video length in seconds (4 to 30)."
299
+ },
300
+ {
301
+ name: "resolution",
302
+ type: "select",
303
+ default: "720p",
304
+ options: ["720p", "1080p", "4k"],
305
+ description: "Output video resolution."
306
+ },
307
+ {
308
+ name: "aspect_ratio",
309
+ type: "select",
310
+ default: "16:9",
311
+ options: ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "adaptive"],
312
+ description: "Aspect ratio of the video."
313
+ },
314
+ {
315
+ name: "bitrate_mode",
316
+ type: "select",
317
+ default: "standard",
318
+ options: ["standard", "high"],
319
+ description: "Bitrate quality setting."
320
+ },
321
+ {
322
+ name: "generate_audio",
323
+ type: "boolean",
324
+ default: true,
325
+ description: "Whether to generate synchronized native audio and sound effects."
326
+ },
327
+ {
328
+ name: "images",
329
+ type: "string[]",
330
+ description: "Array of public HTTP/HTTPS URLs for reference images (up to 30)."
331
+ },
332
+ {
333
+ name: "videos",
334
+ type: "string[]",
335
+ description: "Array of reference video URLs (up to 10; combined duration < 30s)."
336
+ },
337
+ {
338
+ name: "audios",
339
+ type: "string[]",
340
+ description: "Array of reference audio URLs (up to 10; combined duration < 30s)."
341
+ },
342
+ {
343
+ name: "first_frame_image",
344
+ type: "string",
345
+ description: "URL of image for first frame (for image-to-video / first_n_last_frames)."
346
+ },
347
+ {
348
+ name: "last_frame_image",
349
+ type: "string",
350
+ description: "URL of image for last frame (for first_n_last_frames)."
351
+ },
352
+ {
353
+ name: "lipsyncing_audio",
354
+ type: "string",
355
+ description: "Audio URL for lipsyncing or voice clone."
356
+ },
357
+ {
358
+ name: "pass_faces",
359
+ type: "boolean",
360
+ default: false,
361
+ description: "Preserve facial likeness from reference images."
362
+ },
363
+ {
364
+ name: "full_access",
365
+ type: "boolean",
366
+ default: false,
367
+ description: "Enable when generation involves human faces."
368
+ },
369
+ {
370
+ name: "is_uncensored",
371
+ type: "boolean",
372
+ default: false,
373
+ description: "Enable for uncensored / artistic generation."
374
+ }
375
+ ]
376
+ },
377
+ "seedance-2": {
378
+ slug: "seedance-2",
379
+ name: "Seedance 2.0",
380
+ category: "Video",
381
+ kind: "Text-to-Video",
382
+ vendor: "ByteDance",
383
+ description: "Generation-two text-to-video with real-world physics and 1080p native output.",
384
+ priceFormatted: "$0.0768/sec",
385
+ unit: "/sec",
386
+ primaryToolName: "generate_video",
387
+ parameters: [
388
+ {
389
+ name: "prompt",
390
+ type: "string",
391
+ required: true,
392
+ description: "Text prompt describing the desired video."
393
+ },
394
+ {
395
+ name: "quality",
396
+ type: "select",
397
+ default: "standard",
398
+ options: ["standard", "fast", "mini"],
399
+ description: "Quality tier."
400
+ },
401
+ {
402
+ name: "mode",
403
+ type: "select",
404
+ default: "text_to_video",
405
+ options: [
406
+ "text_to_video",
407
+ "first_n_last_frames",
408
+ "multi_reference",
409
+ "edit",
410
+ "extend",
411
+ "multi_frame",
412
+ "lipsyncing",
413
+ "voice_clone",
414
+ "ugc"
415
+ ]
416
+ },
417
+ {
418
+ name: "duration",
419
+ type: "slider",
420
+ default: 5,
421
+ min: 4,
422
+ max: 15,
423
+ description: "Duration in seconds (4 to 15)."
424
+ },
425
+ {
426
+ name: "resolution",
427
+ type: "select",
428
+ default: "720p",
429
+ options: ["720p", "1080p"]
430
+ },
431
+ {
432
+ name: "aspect_ratio",
433
+ type: "select",
434
+ default: "16:9",
435
+ options: ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "adaptive"]
436
+ },
437
+ {
438
+ name: "generate_audio",
439
+ type: "boolean",
440
+ default: false
441
+ },
442
+ {
443
+ name: "images",
444
+ type: "string[]",
445
+ description: "Reference image URLs (up to 9)."
446
+ },
447
+ {
448
+ name: "first_frame_image",
449
+ type: "string",
450
+ description: "Starting frame image URL."
451
+ },
452
+ {
453
+ name: "last_frame_image",
454
+ type: "string",
455
+ description: "End frame image URL."
456
+ }
457
+ ]
458
+ },
459
+ "seedance-2-mini": {
460
+ slug: "seedance-2-mini",
461
+ name: "Seedance 2.0 Mini",
462
+ category: "Video",
463
+ kind: "Text-to-Video",
464
+ vendor: "ByteDance",
465
+ description: "Fast, cost-efficient variant of Seedance 2.0 for rapid prototyping.",
466
+ priceFormatted: "$0.0384/sec",
467
+ unit: "/sec",
468
+ primaryToolName: "generate_video",
469
+ parameters: [
470
+ {
471
+ name: "prompt",
472
+ type: "string",
473
+ required: true,
474
+ description: "Prompt describing the video."
475
+ },
476
+ {
477
+ name: "duration",
478
+ type: "slider",
479
+ default: 5,
480
+ min: 4,
481
+ max: 15
482
+ },
483
+ {
484
+ name: "resolution",
485
+ type: "select",
486
+ default: "720p",
487
+ options: ["720p", "1080p"]
488
+ },
489
+ {
490
+ name: "aspect_ratio",
491
+ type: "select",
492
+ default: "16:9",
493
+ options: ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "adaptive"]
494
+ },
495
+ {
496
+ name: "first_frame_image",
497
+ type: "string",
498
+ description: "Start frame image URL."
499
+ }
500
+ ]
501
+ },
502
+ "wan-3-0": {
503
+ slug: "wan-3-0",
504
+ name: "Wan 3.0",
505
+ category: "Video",
506
+ kind: "Text-to-Video",
507
+ vendor: "Alibaba",
508
+ description: "Alibaba's flagship open video model with exceptional text rendering and complex motion.",
509
+ priceFormatted: "$0.048/sec",
510
+ unit: "/sec",
511
+ primaryToolName: "generate_video",
512
+ parameters: [
513
+ {
514
+ name: "prompt",
515
+ type: "string",
516
+ required: true,
517
+ description: "Detailed description of the scene and motion."
518
+ },
519
+ {
520
+ name: "mode",
521
+ type: "select",
522
+ default: "text_to_video",
523
+ options: ["text_to_video", "image_to_video"]
524
+ },
525
+ {
526
+ name: "aspect_ratio",
527
+ type: "select",
528
+ default: "16:9",
529
+ options: ["16:9", "9:16", "1:1", "4:3", "3:4"]
530
+ },
531
+ {
532
+ name: "resolution",
533
+ type: "select",
534
+ default: "720p",
535
+ options: ["720p", "1080p"]
536
+ },
537
+ {
538
+ name: "duration",
539
+ type: "number",
540
+ default: 5,
541
+ description: "Video duration in seconds (5)."
542
+ },
543
+ {
544
+ name: "image_url",
545
+ type: "string",
546
+ description: "Image URL for image_to_video mode."
547
+ },
548
+ {
549
+ name: "negative_prompt",
550
+ type: "string",
551
+ description: "Elements to avoid in video."
552
+ },
553
+ {
554
+ name: "seed",
555
+ type: "number",
556
+ description: "Random seed for reproducible outputs."
557
+ }
558
+ ]
559
+ },
560
+ "wan-3-0-prime": {
561
+ slug: "wan-3-0-prime",
562
+ name: "Wan 3.0 Prime",
563
+ category: "Video",
564
+ kind: "Text-to-Video",
565
+ vendor: "Alibaba",
566
+ description: "Ultra high-fidelity Wan 3.0 with higher compute budget and refined textures.",
567
+ priceFormatted: "$0.072/sec",
568
+ unit: "/sec",
569
+ primaryToolName: "generate_video",
570
+ parameters: [
571
+ {
572
+ name: "prompt",
573
+ type: "string",
574
+ required: true,
575
+ description: "Detailed description of the scene and motion."
576
+ },
577
+ {
578
+ name: "mode",
579
+ type: "select",
580
+ default: "text_to_video",
581
+ options: ["text_to_video", "image_to_video"]
582
+ },
583
+ {
584
+ name: "aspect_ratio",
585
+ type: "select",
586
+ default: "16:9",
587
+ options: ["16:9", "9:16", "1:1", "4:3", "3:4"]
588
+ },
589
+ {
590
+ name: "resolution",
591
+ type: "select",
592
+ default: "1080p",
593
+ options: ["720p", "1080p"]
594
+ },
595
+ {
596
+ name: "image_url",
597
+ type: "string",
598
+ description: "Image URL for image_to_video mode."
599
+ }
600
+ ]
601
+ },
602
+ "happy-horse-1-1": {
603
+ slug: "happy-horse-1-1",
604
+ name: "Happy Horse 1.1",
605
+ category: "Video",
606
+ kind: "Text-to-Video",
607
+ vendor: "Happy Horse",
608
+ description: "High dynamic range video generation with ultra smooth frame transitions.",
609
+ priceFormatted: "$0.04/sec",
610
+ unit: "/sec",
611
+ primaryToolName: "generate_video",
612
+ parameters: [
613
+ {
614
+ name: "prompt",
615
+ type: "string",
616
+ required: true,
617
+ description: "Text prompt."
618
+ },
619
+ {
620
+ name: "mode",
621
+ type: "select",
622
+ default: "text_to_video",
623
+ options: ["text_to_video", "first_n_last_frames"]
624
+ },
625
+ {
626
+ name: "aspect_ratio",
627
+ type: "select",
628
+ default: "16:9",
629
+ options: ["16:9", "9:16", "1:1", "4:3", "3:4"]
630
+ },
631
+ {
632
+ name: "duration",
633
+ type: "select",
634
+ default: "5",
635
+ options: ["5", "10"]
636
+ },
637
+ {
638
+ name: "first_frame_image",
639
+ type: "string",
640
+ description: "First frame image URL."
641
+ },
642
+ {
643
+ name: "last_frame_image",
644
+ type: "string",
645
+ description: "Last frame image URL."
646
+ }
647
+ ]
648
+ },
649
+ "happy-horse-1-0": {
650
+ slug: "happy-horse-1-0",
651
+ name: "Happy Horse 1.0",
652
+ category: "Video",
653
+ kind: "Text-to-Video",
654
+ vendor: "Happy Horse",
655
+ description: "Fast, high-fidelity video generation.",
656
+ priceFormatted: "$0.03/sec",
657
+ unit: "/sec",
658
+ primaryToolName: "generate_video",
659
+ parameters: [
660
+ {
661
+ name: "prompt",
662
+ type: "string",
663
+ required: true
664
+ },
665
+ {
666
+ name: "aspect_ratio",
667
+ type: "select",
668
+ default: "16:9",
669
+ options: ["16:9", "9:16", "1:1", "4:3", "3:4"]
670
+ },
671
+ {
672
+ name: "duration",
673
+ type: "select",
674
+ default: "5",
675
+ options: ["5", "10"]
676
+ },
677
+ {
678
+ name: "first_frame_image",
679
+ type: "string"
680
+ }
681
+ ]
682
+ },
683
+ "minimax-h3": {
684
+ slug: "minimax-h3",
685
+ name: "Hailuo H3 (MiniMax)",
686
+ category: "Video",
687
+ kind: "Text-to-Video",
688
+ vendor: "MiniMax",
689
+ description: "MiniMax Hailuo video generation with cinematic camera movement and physics.",
690
+ priceFormatted: "$0.06/sec",
691
+ unit: "/sec",
692
+ primaryToolName: "generate_video",
693
+ parameters: [
694
+ {
695
+ name: "prompt",
696
+ type: "string",
697
+ required: true,
698
+ description: "Prompt describing the video scene."
699
+ },
700
+ {
701
+ name: "mode",
702
+ type: "select",
703
+ default: "text_to_video",
704
+ options: ["text_to_video", "image_to_video"]
705
+ },
706
+ {
707
+ name: "image_url",
708
+ type: "string",
709
+ description: "Input image URL for image-to-video."
710
+ },
711
+ {
712
+ name: "duration",
713
+ type: "select",
714
+ default: "6",
715
+ options: ["6", "10"]
716
+ },
717
+ {
718
+ name: "resolution",
719
+ type: "select",
720
+ default: "720p",
721
+ options: ["720p", "1080p"]
722
+ }
723
+ ]
724
+ },
725
+ // Image Models
726
+ "seedream-5-pro": {
727
+ slug: "seedream-5-pro",
728
+ name: "Seedream 5.0 Pro",
729
+ category: "Image",
730
+ kind: "Text-to-Image",
731
+ vendor: "ByteDance",
732
+ description: "ByteDance flagship photorealistic image generation, graphic design, and 3D mesh decomposition.",
733
+ priceFormatted: "$0.018/img",
734
+ unit: "/img",
735
+ primaryToolName: "generate_image",
736
+ parameters: [
737
+ {
738
+ name: "prompt",
739
+ type: "string",
740
+ required: true,
741
+ description: "Detailed description of the image to generate."
742
+ },
743
+ {
744
+ name: "mode",
745
+ type: "select",
746
+ default: "text_to_image",
747
+ options: ["text_to_image", "image_to_image", "layer_decomposition", "text_to_3d"],
748
+ description: "Image generation or 3D / layer mode."
749
+ },
750
+ {
751
+ name: "aspect_ratio",
752
+ type: "select",
753
+ default: "1:1",
754
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21", "3:2", "2:3", "auto"]
755
+ },
756
+ {
757
+ name: "resolution",
758
+ type: "select",
759
+ default: "2K",
760
+ options: ["1K", "2K", "4K"]
761
+ },
762
+ {
763
+ name: "quality",
764
+ type: "select",
765
+ default: "standard",
766
+ options: ["standard", "high"]
767
+ },
768
+ {
769
+ name: "images",
770
+ type: "string[]",
771
+ description: "Input image URLs for image_to_image or layer editing."
772
+ },
773
+ {
774
+ name: "is_uncensored",
775
+ type: "boolean",
776
+ default: false
777
+ }
778
+ ]
779
+ },
780
+ "seedream-5-lite": {
781
+ slug: "seedream-5-lite",
782
+ name: "Seedream 5.0 Lite",
783
+ category: "Image",
784
+ kind: "Text-to-Image",
785
+ vendor: "ByteDance",
786
+ description: "Ultra-fast, budget-friendly Seedream image generation.",
787
+ priceFormatted: "$0.009/img",
788
+ unit: "/img",
789
+ primaryToolName: "generate_image",
790
+ parameters: [
791
+ {
792
+ name: "prompt",
793
+ type: "string",
794
+ required: true
795
+ },
796
+ {
797
+ name: "aspect_ratio",
798
+ type: "select",
799
+ default: "1:1",
800
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21", "3:2", "2:3", "auto"]
801
+ },
802
+ {
803
+ name: "resolution",
804
+ type: "select",
805
+ default: "1K",
806
+ options: ["1K", "2K"]
807
+ },
808
+ {
809
+ name: "images",
810
+ type: "string[]"
811
+ }
812
+ ]
813
+ },
814
+ "gpt-image-2": {
815
+ slug: "gpt-image-2",
816
+ name: "GPT Image 2",
817
+ category: "Image",
818
+ kind: "Text-to-Image",
819
+ vendor: "OpenAI",
820
+ description: "Precision image generation with accurate typography, prompt following, and multi-image editing.",
821
+ priceFormatted: "$0.016/img",
822
+ unit: "/img",
823
+ primaryToolName: "generate_image",
824
+ parameters: [
825
+ {
826
+ name: "prompt",
827
+ type: "string",
828
+ required: true,
829
+ description: "Image prompt text. Supports complex composition and text instructions."
830
+ },
831
+ {
832
+ name: "aspect_ratio",
833
+ type: "select",
834
+ default: "1:1",
835
+ options: [
836
+ "1:1",
837
+ "1:2",
838
+ "2:1",
839
+ "2:3",
840
+ "3:2",
841
+ "3:4",
842
+ "4:3",
843
+ "4:5",
844
+ "5:4",
845
+ "9:16",
846
+ "16:9",
847
+ "21:9",
848
+ "9:21",
849
+ "auto"
850
+ ]
851
+ },
852
+ {
853
+ name: "resolution",
854
+ type: "select",
855
+ default: "1K",
856
+ options: ["1K", "2K"]
857
+ },
858
+ {
859
+ name: "quality",
860
+ type: "select",
861
+ default: "auto",
862
+ options: ["auto", "low", "medium", "high"]
863
+ },
864
+ {
865
+ name: "input_images",
866
+ type: "string[]",
867
+ description: "Image URLs to modify or use as reference."
868
+ },
869
+ {
870
+ name: "is_uncensored",
871
+ type: "boolean",
872
+ default: false
873
+ }
874
+ ]
875
+ },
876
+ "gpt-image-2.5-flare": {
877
+ slug: "gpt-image-2.5-flare",
878
+ name: "GPT Image 2.5 Flare",
879
+ category: "Image",
880
+ kind: "Text-to-Image",
881
+ vendor: "OpenAI",
882
+ description: "Enhanced color vibrancy, lens effects, and studio lighting control.",
883
+ priceFormatted: "$0.022/img",
884
+ unit: "/img",
885
+ primaryToolName: "generate_image",
886
+ parameters: [
887
+ {
888
+ name: "prompt",
889
+ type: "string",
890
+ required: true
891
+ },
892
+ {
893
+ name: "aspect_ratio",
894
+ type: "select",
895
+ default: "16:9",
896
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "auto"]
897
+ },
898
+ {
899
+ name: "resolution",
900
+ type: "select",
901
+ default: "2K",
902
+ options: ["1K", "2K"]
903
+ }
904
+ ]
905
+ },
906
+ "gpt-image-2.5-sunburst": {
907
+ slug: "gpt-image-2.5-sunburst",
908
+ name: "GPT Image 2.5 Sunburst",
909
+ category: "Image",
910
+ kind: "Text-to-Image",
911
+ vendor: "OpenAI",
912
+ description: "High-contrast cinematic photography and art render engine.",
913
+ priceFormatted: "$0.024/img",
914
+ unit: "/img",
915
+ primaryToolName: "generate_image",
916
+ parameters: [
917
+ {
918
+ name: "prompt",
919
+ type: "string",
920
+ required: true
921
+ },
922
+ {
923
+ name: "aspect_ratio",
924
+ type: "select",
925
+ default: "16:9",
926
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "auto"]
927
+ },
928
+ {
929
+ name: "resolution",
930
+ type: "select",
931
+ default: "2K",
932
+ options: ["1K", "2K"]
933
+ }
934
+ ]
935
+ },
936
+ "grok-imagine-2": {
937
+ slug: "grok-imagine-2",
938
+ name: "Grok Imagine 2",
939
+ category: "Image",
940
+ kind: "Text-to-Image",
941
+ vendor: "xAI",
942
+ description: "xAI's fast, uncensored creative image generation engine.",
943
+ priceFormatted: "$0.012/img",
944
+ unit: "/img",
945
+ primaryToolName: "generate_image",
946
+ parameters: [
947
+ {
948
+ name: "prompt",
949
+ type: "string",
950
+ required: true,
951
+ description: "Text prompt."
952
+ },
953
+ {
954
+ name: "aspect_ratio",
955
+ type: "select",
956
+ default: "1:1",
957
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "2:3", "3:2"]
958
+ },
959
+ {
960
+ name: "resolution",
961
+ type: "select",
962
+ default: "1K",
963
+ options: ["1K", "2K"]
964
+ },
965
+ {
966
+ name: "is_uncensored",
967
+ type: "boolean",
968
+ default: false
969
+ }
970
+ ]
971
+ },
972
+ "nano-banana-2": {
973
+ slug: "nano-banana-2",
974
+ name: "Nano Banana 2",
975
+ category: "Image",
976
+ kind: "Text-to-Image",
977
+ vendor: "Nano",
978
+ description: "Compact high-speed image generator tuned for product shots and digital art.",
979
+ priceFormatted: "$0.008/img",
980
+ unit: "/img",
981
+ primaryToolName: "generate_image",
982
+ parameters: [
983
+ {
984
+ name: "prompt",
985
+ type: "string",
986
+ required: true
987
+ },
988
+ {
989
+ name: "aspect_ratio",
990
+ type: "select",
991
+ default: "1:1",
992
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "21:9"]
993
+ },
994
+ {
995
+ name: "resolution",
996
+ type: "select",
997
+ default: "1K",
998
+ options: ["1K", "2K"]
999
+ }
1000
+ ]
1001
+ },
1002
+ "nano-banana-2-lite": {
1003
+ slug: "nano-banana-2-lite",
1004
+ name: "Nano Banana 2 Lite",
1005
+ category: "Image",
1006
+ kind: "Text-to-Image",
1007
+ vendor: "Nano",
1008
+ description: "Ultra fast sub-second image rendering for thumbnails and concepts.",
1009
+ priceFormatted: "$0.004/img",
1010
+ unit: "/img",
1011
+ primaryToolName: "generate_image",
1012
+ parameters: [
1013
+ {
1014
+ name: "prompt",
1015
+ type: "string",
1016
+ required: true
1017
+ },
1018
+ {
1019
+ name: "aspect_ratio",
1020
+ type: "select",
1021
+ default: "1:1",
1022
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4"]
1023
+ }
1024
+ ]
1025
+ },
1026
+ "nano-banana-pro": {
1027
+ slug: "nano-banana-pro",
1028
+ name: "Nano Banana Pro",
1029
+ category: "Image",
1030
+ kind: "Text-to-Image",
1031
+ vendor: "Nano",
1032
+ description: "High-res studio rendering with granular lighting controls.",
1033
+ priceFormatted: "$0.014/img",
1034
+ unit: "/img",
1035
+ primaryToolName: "generate_image",
1036
+ parameters: [
1037
+ {
1038
+ name: "prompt",
1039
+ type: "string",
1040
+ required: true
1041
+ },
1042
+ {
1043
+ name: "aspect_ratio",
1044
+ type: "select",
1045
+ default: "1:1",
1046
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "21:9"]
1047
+ },
1048
+ {
1049
+ name: "resolution",
1050
+ type: "select",
1051
+ default: "2K",
1052
+ options: ["1K", "2K"]
1053
+ }
1054
+ ]
1055
+ },
1056
+ "seed-2-1-turbo": {
1057
+ slug: "seed-2-1-turbo",
1058
+ name: "Seed 2.1 Turbo",
1059
+ category: "Image",
1060
+ kind: "Text-to-Image",
1061
+ vendor: "ByteDance",
1062
+ description: "High-speed image generation with low latency and rich detail.",
1063
+ priceFormatted: "$0.006/img",
1064
+ unit: "/img",
1065
+ primaryToolName: "generate_image",
1066
+ parameters: [
1067
+ {
1068
+ name: "prompt",
1069
+ type: "string",
1070
+ required: true
1071
+ },
1072
+ {
1073
+ name: "aspect_ratio",
1074
+ type: "select",
1075
+ default: "1:1",
1076
+ options: ["1:1", "16:9", "9:16", "4:3", "3:4"]
1077
+ },
1078
+ {
1079
+ name: "resolution",
1080
+ type: "select",
1081
+ default: "1K",
1082
+ options: ["1K", "2K"]
1083
+ }
1084
+ ]
1085
+ },
1086
+ // LLM / Reasoning
1087
+ "gpt-6-astra": {
1088
+ slug: "gpt-6-astra",
1089
+ name: "GPT-6 Astra",
1090
+ category: "Text",
1091
+ kind: "Reasoning (LLM)",
1092
+ vendor: "Tein / Frontier",
1093
+ description: "Frontier reasoning model with deep step-by-step logic, code synthesis, and analytical capabilities.",
1094
+ priceFormatted: "$0.40/M in \xB7 $1.60/M out",
1095
+ unit: "/M tokens",
1096
+ primaryToolName: "run_task",
1097
+ parameters: [
1098
+ {
1099
+ name: "prompt",
1100
+ type: "string",
1101
+ required: true,
1102
+ description: "The user query, instruction, or reasoning task."
1103
+ },
1104
+ {
1105
+ name: "system_prompt",
1106
+ type: "string",
1107
+ description: "Optional system instructions/persona."
1108
+ },
1109
+ {
1110
+ name: "temperature",
1111
+ type: "slider",
1112
+ default: 0.7,
1113
+ min: 0,
1114
+ max: 2,
1115
+ description: "Sampling temperature."
1116
+ },
1117
+ {
1118
+ name: "max_tokens",
1119
+ type: "number",
1120
+ default: 4096,
1121
+ min: 1,
1122
+ max: 65536,
1123
+ description: "Maximum tokens to generate."
1124
+ }
1125
+ ]
1126
+ },
1127
+ "gemini-3-8-flash": {
1128
+ slug: "gemini-3-8-flash",
1129
+ name: "Gemini 3.8 Flash",
1130
+ category: "Text",
1131
+ kind: "Reasoning (LLM)",
1132
+ vendor: "Google",
1133
+ description: "Google's ultra-fast multimodal reasoning model with massive context capabilities.",
1134
+ priceFormatted: "$0.08/M in \xB7 $0.32/M out",
1135
+ unit: "/M tokens",
1136
+ primaryToolName: "run_task",
1137
+ parameters: [
1138
+ {
1139
+ name: "prompt",
1140
+ type: "string",
1141
+ required: true
1142
+ },
1143
+ {
1144
+ name: "system_prompt",
1145
+ type: "string"
1146
+ },
1147
+ {
1148
+ name: "temperature",
1149
+ type: "slider",
1150
+ default: 0.7,
1151
+ min: 0,
1152
+ max: 2
1153
+ },
1154
+ {
1155
+ name: "max_tokens",
1156
+ type: "number",
1157
+ default: 4096
1158
+ }
1159
+ ]
1160
+ },
1161
+ "gemini-3-7-flash": {
1162
+ slug: "gemini-3-7-flash",
1163
+ name: "Gemini 3.7 Flash",
1164
+ category: "Text",
1165
+ kind: "Reasoning (LLM)",
1166
+ vendor: "Google",
1167
+ description: "Hybrid reasoning and instant response model with native tool calling.",
1168
+ priceFormatted: "$0.07/M in \xB7 $0.28/M out",
1169
+ unit: "/M tokens",
1170
+ primaryToolName: "run_task",
1171
+ parameters: [
1172
+ {
1173
+ name: "prompt",
1174
+ type: "string",
1175
+ required: true
1176
+ },
1177
+ {
1178
+ name: "system_prompt",
1179
+ type: "string"
1180
+ },
1181
+ {
1182
+ name: "temperature",
1183
+ type: "slider",
1184
+ default: 0.7,
1185
+ min: 0,
1186
+ max: 2
1187
+ }
1188
+ ]
1189
+ },
1190
+ "gemini-3-1-pro-preview": {
1191
+ slug: "gemini-3-1-pro-preview",
1192
+ name: "Gemini 3.1 Pro Preview",
1193
+ category: "Text",
1194
+ kind: "Reasoning (LLM)",
1195
+ vendor: "Google",
1196
+ description: "Frontier code and logic reasoning preview model.",
1197
+ priceFormatted: "$0.30/M in \xB7 $1.20/M out",
1198
+ unit: "/M tokens",
1199
+ primaryToolName: "run_task",
1200
+ parameters: [
1201
+ {
1202
+ name: "prompt",
1203
+ type: "string",
1204
+ required: true
1205
+ },
1206
+ {
1207
+ name: "system_prompt",
1208
+ type: "string"
1209
+ }
1210
+ ]
1211
+ },
1212
+ "qwen-3": {
1213
+ slug: "qwen-3",
1214
+ name: "Qwen 3",
1215
+ category: "Text",
1216
+ kind: "Reasoning (LLM)",
1217
+ vendor: "Alibaba",
1218
+ description: "High-capability multilingual reasoning and agent model.",
1219
+ priceFormatted: "$0.15/M in \xB7 $0.60/M out",
1220
+ unit: "/M tokens",
1221
+ primaryToolName: "run_task",
1222
+ parameters: [
1223
+ {
1224
+ name: "prompt",
1225
+ type: "string",
1226
+ required: true
1227
+ },
1228
+ {
1229
+ name: "system_prompt",
1230
+ type: "string"
1231
+ }
1232
+ ]
1233
+ },
1234
+ "qwen-3-pro": {
1235
+ slug: "qwen-3-pro",
1236
+ name: "Qwen 3 Pro",
1237
+ category: "Text",
1238
+ kind: "Reasoning (LLM)",
1239
+ vendor: "Alibaba",
1240
+ description: "Deep reasoning and complex coding model with mathematical rigor.",
1241
+ priceFormatted: "$0.35/M in \xB7 $1.40/M out",
1242
+ unit: "/M tokens",
1243
+ primaryToolName: "run_task",
1244
+ parameters: [
1245
+ {
1246
+ name: "prompt",
1247
+ type: "string",
1248
+ required: true
1249
+ },
1250
+ {
1251
+ name: "system_prompt",
1252
+ type: "string"
1253
+ }
1254
+ ]
1255
+ },
1256
+ // Audio / TTS
1257
+ "seed-audio-1-0": {
1258
+ slug: "seed-audio-1-0",
1259
+ name: "Seed Audio 1.0",
1260
+ category: "Audio",
1261
+ kind: "Text-to-Audio",
1262
+ vendor: "ByteDance",
1263
+ description: "Natural emotive voice cloning, studio TTS, and multi-speaker speech synthesis.",
1264
+ priceFormatted: "$0.002/sec",
1265
+ unit: "/sec",
1266
+ primaryToolName: "generate_audio",
1267
+ parameters: [
1268
+ {
1269
+ name: "prompt",
1270
+ type: "string",
1271
+ required: true,
1272
+ description: "Text script to speak or audio sound effect description."
1273
+ },
1274
+ {
1275
+ name: "speaker",
1276
+ type: "select",
1277
+ default: "en_female_warm",
1278
+ options: [
1279
+ "en_female_warm",
1280
+ "en_male_deep",
1281
+ "en_female_energetic",
1282
+ "en_male_narrator",
1283
+ "zh_female_natural",
1284
+ "zh_male_news"
1285
+ ],
1286
+ description: "Voice persona / speaker ID."
1287
+ },
1288
+ {
1289
+ name: "format",
1290
+ type: "select",
1291
+ default: "wav",
1292
+ options: ["wav", "mp3", "pcm", "ogg_opus"],
1293
+ description: "Output audio format."
1294
+ },
1295
+ {
1296
+ name: "sample_rate",
1297
+ type: "select",
1298
+ default: "24000",
1299
+ options: ["8000", "16000", "24000", "32000", "44100", "48000"]
1300
+ },
1301
+ {
1302
+ name: "speech_rate",
1303
+ type: "slider",
1304
+ default: 0,
1305
+ min: -50,
1306
+ max: 100,
1307
+ description: "Speech speed (-50 is 0.5x, 100 is 2.0x)."
1308
+ },
1309
+ {
1310
+ name: "loudness_rate",
1311
+ type: "slider",
1312
+ default: 0,
1313
+ min: -50,
1314
+ max: 100,
1315
+ description: "Loudness/volume (-50 to 100)."
1316
+ },
1317
+ {
1318
+ name: "pitch_rate",
1319
+ type: "slider",
1320
+ default: 0,
1321
+ min: -12,
1322
+ max: 12,
1323
+ description: "Voice pitch offset (-12 to 12)."
1324
+ },
1325
+ {
1326
+ name: "enable_subtitle",
1327
+ type: "boolean",
1328
+ default: false,
1329
+ description: "Return word-level timestamps."
1330
+ }
1331
+ ]
1332
+ }
1333
+ };
1334
+ function getMcpModel(slug) {
1335
+ return MCP_MODELS[slug] ?? null;
1336
+ }
1337
+ function listMcpModels() {
1338
+ return Object.values(MCP_MODELS);
1339
+ }
1340
+
1341
+ // src/cli/commands/run.ts
1342
+ function parseCliArgs(args) {
1343
+ const options = {};
1344
+ const flags = /* @__PURE__ */ new Set();
1345
+ const positional = [];
1346
+ for (let i = 0; i < args.length; i++) {
1347
+ const arg = args[i];
1348
+ if (arg.startsWith("--")) {
1349
+ const key = arg.slice(2);
1350
+ if (key.includes("=")) {
1351
+ const [k, v] = key.split("=", 2);
1352
+ options[k.replace(/-/g, "_")] = v;
1353
+ } else if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
1354
+ const val = args[i + 1];
1355
+ if (val === "true") options[key.replace(/-/g, "_")] = true;
1356
+ else if (val === "false") options[key.replace(/-/g, "_")] = false;
1357
+ else if (/^-?\d+(\.\d+)?$/.test(val)) options[key.replace(/-/g, "_")] = Number(val);
1358
+ else options[key.replace(/-/g, "_")] = val;
1359
+ i++;
1360
+ } else {
1361
+ options[key.replace(/-/g, "_")] = true;
1362
+ flags.add(key);
1363
+ }
1364
+ } else if (arg.startsWith("-") && arg.length === 2) {
1365
+ const shortKey = arg[1];
1366
+ const map = {
1367
+ p: "prompt",
1368
+ o: "output",
1369
+ d: "duration",
1370
+ r: "resolution",
1371
+ q: "quality",
1372
+ s: "speaker",
1373
+ j: "json",
1374
+ h: "help",
1375
+ v: "version"
1376
+ };
1377
+ const longKey = map[shortKey] || shortKey;
1378
+ if (shortKey === "j" || shortKey === "h" || shortKey === "v") {
1379
+ options[longKey] = true;
1380
+ flags.add(longKey);
1381
+ } else if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
1382
+ options[longKey] = args[i + 1];
1383
+ i++;
1384
+ } else {
1385
+ options[longKey] = true;
1386
+ }
1387
+ } else {
1388
+ positional.push(arg);
1389
+ }
1390
+ }
1391
+ return {
1392
+ command: positional[0] || "",
1393
+ subcommand: positional[1],
1394
+ modelSlug: positional[1] || positional[0],
1395
+ options,
1396
+ flags
1397
+ };
1398
+ }
1399
+ async function runCommand(modelSlug, options) {
1400
+ const apiKey = resolveApiKey(typeof options.api_key === "string" ? options.api_key : void 0);
1401
+ const apiUrl = resolveApiUrl(typeof options.api_url === "string" ? options.api_url : void 0);
1402
+ if (!apiKey) {
1403
+ process.stderr.write(`${c.red("Error:")} Authentication required.
1404
+ `);
1405
+ process.stderr.write(`Please run ${c.cyan("tein auth login")} or set ${c.cyan("TEIN_API_KEY")} environment variable.
1406
+ `);
1407
+ process.exit(1);
1408
+ }
1409
+ if (!modelSlug) {
1410
+ process.stderr.write(`${c.red("Error:")} Missing model slug.
1411
+ `);
1412
+ process.stderr.write(`Usage: ${c.cyan('tein run <model-slug> --prompt "..."')}
1413
+ `);
1414
+ process.stderr.write(`Run ${c.cyan("tein models")} to list all available models.
1415
+ `);
1416
+ process.exit(1);
1417
+ }
1418
+ const modelMeta = getMcpModel(modelSlug);
1419
+ const isJson = Boolean(options.json);
1420
+ const isAsync = Boolean(options.async || options.no_wait);
1421
+ const outputFile = typeof options.output === "string" ? options.output : void 0;
1422
+ const parameters = { ...options };
1423
+ delete parameters.json;
1424
+ delete parameters.async;
1425
+ delete parameters.no_wait;
1426
+ delete parameters.output;
1427
+ delete parameters.api_key;
1428
+ delete parameters.api_url;
1429
+ if (modelMeta?.parameters) {
1430
+ for (const param of modelMeta.parameters) {
1431
+ if (param.default !== void 0 && parameters[param.name] === void 0) {
1432
+ parameters[param.name] = param.default;
1433
+ }
1434
+ }
1435
+ }
1436
+ if (typeof parameters.images === "string") {
1437
+ parameters.images = [parameters.images];
1438
+ }
1439
+ if (typeof parameters.image === "string" && !parameters.images) {
1440
+ parameters.images = [parameters.image];
1441
+ delete parameters.image;
1442
+ }
1443
+ if (parameters.image_url && !parameters.first_frame_image && (modelSlug.startsWith("seedance") || modelSlug.startsWith("happy-horse"))) {
1444
+ parameters.first_frame_image = parameters.image_url;
1445
+ }
1446
+ const client = new TeinApiClient(apiKey, apiUrl);
1447
+ const spinner = new Spinner(`Submitting task for ${c.bold(modelSlug)}...`);
1448
+ if (!isJson) {
1449
+ spinner.start();
1450
+ }
1451
+ let job;
1452
+ try {
1453
+ job = await client.runJob(modelSlug, parameters);
1454
+ } catch (err) {
1455
+ if (!isJson) spinner.fail(`Failed to start generation: ${err instanceof Error ? err.message : err}`);
1456
+ else process.stdout.write(JSON.stringify({ error: String(err) }, null, 2) + "\n");
1457
+ process.exit(1);
1458
+ }
1459
+ if (isAsync) {
1460
+ if (!isJson) {
1461
+ spinner.succeed(`Queued successfully! Request ID: ${c.bold(job.request_id)}`);
1462
+ process.stdout.write(`Check status anytime with: ${c.cyan(`tein status ${job.request_id}`)}
1463
+ `);
1464
+ } else {
1465
+ process.stdout.write(JSON.stringify(job, null, 2) + "\n");
1466
+ }
1467
+ return;
1468
+ }
1469
+ const startTime = Date.now();
1470
+ let pollInterval = 1500;
1471
+ while (job.status === "processing") {
1472
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
1473
+ if (!isJson) {
1474
+ spinner.update(`Rendering ${c.bold(modelSlug)} (${elapsed}s elapsed)... [ID: ${job.request_id}]`);
1475
+ }
1476
+ await new Promise((r) => setTimeout(r, pollInterval));
1477
+ try {
1478
+ job = await client.getStatus(job.request_id);
1479
+ } catch (err) {
1480
+ }
1481
+ }
1482
+ if (job.status === "failed") {
1483
+ if (!isJson) spinner.fail(`Generation failed: ${job.error || "Unknown error"}`);
1484
+ else process.stdout.write(JSON.stringify(job, null, 2) + "\n");
1485
+ process.exit(1);
1486
+ }
1487
+ if (!isJson) {
1488
+ spinner.succeed(`Generation completed for ${c.bold(modelMeta?.name || modelSlug)}!`);
1489
+ }
1490
+ if (isJson) {
1491
+ process.stdout.write(JSON.stringify(job, null, 2) + "\n");
1492
+ return;
1493
+ }
1494
+ process.stdout.write("\n");
1495
+ process.stdout.write(` ${c.bold("Status:")} ${c.green("Succeeded")}
1496
+ `);
1497
+ process.stdout.write(` ${c.bold("Request ID:")} ${job.request_id}
1498
+ `);
1499
+ if (job.usage?.cost !== void 0) {
1500
+ process.stdout.write(` ${c.bold("Cost:")} ${job.usage.cost.toFixed(4)} credits
1501
+ `);
1502
+ }
1503
+ if (job.output && job.output.length > 0) {
1504
+ process.stdout.write(`
1505
+ ${c.bold("Outputs:")}
1506
+ `);
1507
+ for (let idx = 0; idx < job.output.length; idx++) {
1508
+ const item = job.output[idx];
1509
+ if (item.url) {
1510
+ process.stdout.write(` [${item.type.toUpperCase()}] ${c.cyan(c.underline(item.url))}
1511
+ `);
1512
+ if (outputFile) {
1513
+ const dlSpinner = new Spinner(`Downloading output to ${outputFile}...`).start();
1514
+ try {
1515
+ await client.downloadOutput(item.url, import_node_path3.default.resolve(process.cwd(), outputFile));
1516
+ dlSpinner.succeed(`Saved to ${c.bold(outputFile)}`);
1517
+ } catch (dlErr) {
1518
+ dlSpinner.fail(`Failed to download: ${dlErr instanceof Error ? dlErr.message : dlErr}`);
1519
+ }
1520
+ }
1521
+ } else if (item.text) {
1522
+ process.stdout.write(`
1523
+ ${item.text}
1524
+ `);
1525
+ }
1526
+ }
1527
+ }
1528
+ process.stdout.write("\n");
1529
+ }
1530
+
1531
+ // src/cli/commands/auth.ts
1532
+ var import_node_readline = __toESM(require("node:readline"));
1533
+ function promptLine(promptText) {
1534
+ const rl = import_node_readline.default.createInterface({
1535
+ input: process.stdin,
1536
+ output: process.stdout
1537
+ });
1538
+ return new Promise((resolve) => {
1539
+ rl.question(promptText, (answer) => {
1540
+ rl.close();
1541
+ resolve(answer.trim());
1542
+ });
1543
+ });
1544
+ }
1545
+ async function authCommand(subcommand, argValue) {
1546
+ switch (subcommand) {
1547
+ case "login":
1548
+ case "set-key": {
1549
+ let key = argValue?.trim() || "";
1550
+ if (!key) {
1551
+ process.stdout.write(`
1552
+ ${c.bold("Tein AI Authentication")}
1553
+ `);
1554
+ process.stdout.write(`Get your API key at: ${c.cyan("https://tein.ai/dashboard/keys")}
1555
+
1556
+ `);
1557
+ key = await promptLine("Enter your Tein API Key (sk_live_...): ");
1558
+ }
1559
+ if (!key.startsWith("sk_live_") && !key.startsWith("sk_test_")) {
1560
+ process.stderr.write(
1561
+ `${c.red("Error:")} Invalid key format. Tein API keys start with 'sk_live_' or 'sk_test_'.
1562
+ `
1563
+ );
1564
+ process.exit(1);
1565
+ }
1566
+ saveStoredConfig({ apiKey: key });
1567
+ process.stdout.write(`
1568
+ ${c.green("\u2714")} API key saved to ${c.dim(getConfigPath())}
1569
+ `);
1570
+ process.stdout.write(`You can now run models directly using: ${c.cyan('tein run <model-slug> --prompt "..."')}
1571
+
1572
+ `);
1573
+ break;
1574
+ }
1575
+ case "logout": {
1576
+ saveStoredConfig({ apiKey: "" });
1577
+ process.stdout.write(`${c.green("\u2714")} Logged out. Stored API key has been cleared.
1578
+ `);
1579
+ break;
1580
+ }
1581
+ case "status":
1582
+ case "whoami":
1583
+ default: {
1584
+ const apiKey = resolveApiKey();
1585
+ const apiUrl = resolveApiUrl();
1586
+ const config = getStoredConfig();
1587
+ process.stdout.write(`
1588
+ ${c.bold("Tein AI CLI Configuration")}
1589
+ `);
1590
+ process.stdout.write(` Config file: ${c.dim(getConfigPath())}
1591
+ `);
1592
+ process.stdout.write(` API URL: ${c.cyan(apiUrl)}
1593
+ `);
1594
+ if (!apiKey) {
1595
+ process.stdout.write(` API Key: ${c.red("Not configured")}
1596
+
1597
+ `);
1598
+ process.stdout.write(`Run ${c.cyan("tein auth login")} or set ${c.cyan("TEIN_API_KEY")} environment variable.
1599
+
1600
+ `);
1601
+ } else {
1602
+ const masked = `${apiKey.slice(0, 10)}...${apiKey.slice(-4)}`;
1603
+ process.stdout.write(` API Key: ${c.green(masked)} (${apiKey.startsWith("sk_live_") ? "Live Key" : "Test Key"})
1604
+
1605
+ `);
1606
+ }
1607
+ break;
1608
+ }
1609
+ }
1610
+ }
1611
+
1612
+ // src/cli/commands/models.ts
1613
+ function listModelsCommand(categoryFilter) {
1614
+ let models = listMcpModels();
1615
+ if (categoryFilter) {
1616
+ const filterLower = categoryFilter.toLowerCase();
1617
+ models = models.filter(
1618
+ (m) => m.category.toLowerCase() === filterLower || m.kind.toLowerCase().includes(filterLower) || m.slug.toLowerCase().includes(filterLower)
1619
+ );
1620
+ }
1621
+ process.stdout.write(`
1622
+ ${c.bold("Tein AI Models Catalog")} (${models.length} available)
1623
+ `);
1624
+ process.stdout.write(`${c.dim("Run `tein info <model-slug>` to see detailed parameters and options.")}
1625
+
1626
+ `);
1627
+ const categoryGroups = {};
1628
+ for (const model of models) {
1629
+ const cat = model.category;
1630
+ if (!categoryGroups[cat]) categoryGroups[cat] = [];
1631
+ categoryGroups[cat].push(model);
1632
+ }
1633
+ const categoryOrder = ["Video", "Image", "Text", "Audio", "3D"];
1634
+ for (const cat of categoryOrder) {
1635
+ const list = categoryGroups[cat];
1636
+ if (!list || list.length === 0) continue;
1637
+ process.stdout.write(`${c.bold(c.cyan(`\u2500\u2500 ${cat} Models \u2500\u2500`))}
1638
+ `);
1639
+ for (const m of list) {
1640
+ const slugPadded = m.slug.padEnd(24);
1641
+ const namePadded = m.name.padEnd(22);
1642
+ const pricePadded = m.priceFormatted.padEnd(22);
1643
+ process.stdout.write(
1644
+ ` ${c.bold(slugPadded)} ${c.gray(namePadded)} ${c.green(pricePadded)} ${c.dim(m.vendor)}
1645
+ `
1646
+ );
1647
+ }
1648
+ process.stdout.write("\n");
1649
+ }
1650
+ process.stdout.write(
1651
+ `${c.dim("Tip:")} Generate directly with ${c.cyan('tein run <model-slug> --prompt "..."')}
1652
+
1653
+ `
1654
+ );
1655
+ }
1656
+ function modelInfoCommand(slug) {
1657
+ const model = getMcpModel(slug);
1658
+ if (!model) {
1659
+ process.stderr.write(`${c.red("Error:")} Model "${slug}" not found in Tein AI registry.
1660
+ `);
1661
+ process.stderr.write(`Run ${c.cyan("tein models")} to see all available model slugs.
1662
+ `);
1663
+ process.exit(1);
1664
+ }
1665
+ process.stdout.write(`
1666
+ ${c.bold(c.cyan(model.name))} (${c.bold(model.slug)})
1667
+ `);
1668
+ process.stdout.write(` ${c.gray(model.description)}
1669
+
1670
+ `);
1671
+ process.stdout.write(`${c.bold("Metadata:")}
1672
+ `);
1673
+ process.stdout.write(` Category: ${c.yellow(model.category)}
1674
+ `);
1675
+ process.stdout.write(` Kind: ${model.kind}
1676
+ `);
1677
+ process.stdout.write(` Vendor: ${model.vendor}
1678
+ `);
1679
+ process.stdout.write(` Pricing: ${c.green(model.priceFormatted)}
1680
+
1681
+ `);
1682
+ process.stdout.write(`${c.bold("Parameters:")}
1683
+ `);
1684
+ for (const param of model.parameters) {
1685
+ const reqBadge = param.required ? c.red("(required)") : c.dim("(optional)");
1686
+ const defaultVal = param.default !== void 0 ? c.dim(` [default: ${param.default}]`) : "";
1687
+ process.stdout.write(
1688
+ ` ${c.bold(`--${param.name.replace(/_/g, "-")}`)} ${c.yellow(`<${param.type}>`)} ${reqBadge}${defaultVal}
1689
+ `
1690
+ );
1691
+ if (param.description) {
1692
+ process.stdout.write(` ${c.gray(param.description)}
1693
+ `);
1694
+ }
1695
+ if (param.options && param.options.length > 0) {
1696
+ process.stdout.write(` Allowed values: ${c.dim(param.options.join(", "))}
1697
+ `);
1698
+ }
1699
+ if (param.min !== void 0 || param.max !== void 0) {
1700
+ process.stdout.write(` Range: ${param.min ?? ""} to ${param.max ?? ""}
1701
+ `);
1702
+ }
1703
+ process.stdout.write("\n");
1704
+ }
1705
+ process.stdout.write(`${c.bold("Sample CLI Usage:")}
1706
+ `);
1707
+ if (model.category === "Video") {
1708
+ process.stdout.write(
1709
+ ` ${c.cyan(`tein run ${model.slug} --prompt "Cinematic camera drone shot of a coastline" --aspect-ratio 16:9 --duration 5 -o output.mp4`)}
1710
+
1711
+ `
1712
+ );
1713
+ } else if (model.category === "Image") {
1714
+ process.stdout.write(
1715
+ ` ${c.cyan(`tein run ${model.slug} --prompt "Cyberpunk city in the rain, 8k render" --aspect-ratio 16:9 -o output.png`)}
1716
+
1717
+ `
1718
+ );
1719
+ } else if (model.category === "Audio") {
1720
+ process.stdout.write(
1721
+ ` ${c.cyan(`tein run ${model.slug} --prompt "Hello world from Tein AI" --speaker en_female_warm -o speech.wav`)}
1722
+
1723
+ `
1724
+ );
1725
+ } else {
1726
+ process.stdout.write(
1727
+ ` ${c.cyan(`tein run ${model.slug} --prompt "Explain quantum computing in simple terms"`)}
1728
+
1729
+ `
1730
+ );
1731
+ }
1732
+ }
1733
+
1734
+ // src/cli/commands/status.ts
1735
+ var import_node_path4 = __toESM(require("node:path"));
1736
+ async function statusCommand(requestId, options) {
1737
+ const apiKey = resolveApiKey(typeof options.api_key === "string" ? options.api_key : void 0);
1738
+ const apiUrl = resolveApiUrl(typeof options.api_url === "string" ? options.api_url : void 0);
1739
+ if (!apiKey) {
1740
+ process.stderr.write(`${c.red("Error:")} Authentication required.
1741
+ `);
1742
+ process.stderr.write(`Please run ${c.cyan("tein auth login")} or set ${c.cyan("TEIN_API_KEY")} environment variable.
1743
+ `);
1744
+ process.exit(1);
1745
+ }
1746
+ if (!requestId) {
1747
+ process.stderr.write(`${c.red("Error:")} Missing request ID.
1748
+ `);
1749
+ process.stderr.write(`Usage: ${c.cyan("tein status <request-id> [--wait] [-o output.mp4]")}
1750
+ `);
1751
+ process.exit(1);
1752
+ }
1753
+ const isJson = Boolean(options.json);
1754
+ const shouldWait = Boolean(options.wait);
1755
+ const outputFile = typeof options.output === "string" ? options.output : void 0;
1756
+ const client = new TeinApiClient(apiKey, apiUrl);
1757
+ const spinner = new Spinner(`Checking status of ${c.bold(requestId)}...`);
1758
+ if (!isJson) spinner.start();
1759
+ let job = await client.getStatus(requestId);
1760
+ if (shouldWait && job.status === "processing") {
1761
+ const startTime = Date.now();
1762
+ while (job.status === "processing") {
1763
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
1764
+ if (!isJson) {
1765
+ spinner.update(`Waiting for ${c.bold(job.model)} (${elapsed}s elapsed)...`);
1766
+ }
1767
+ await new Promise((r) => setTimeout(r, 1500));
1768
+ try {
1769
+ job = await client.getStatus(requestId);
1770
+ } catch {
1771
+ }
1772
+ }
1773
+ }
1774
+ if (!isJson) {
1775
+ if (job.status === "succeeded") {
1776
+ spinner.succeed(`Task completed!`);
1777
+ } else if (job.status === "failed") {
1778
+ spinner.fail(`Task failed: ${job.error || "Unknown error"}`);
1779
+ } else {
1780
+ spinner.stop();
1781
+ }
1782
+ }
1783
+ if (isJson) {
1784
+ process.stdout.write(JSON.stringify(job, null, 2) + "\n");
1785
+ return;
1786
+ }
1787
+ process.stdout.write(`
1788
+ ${c.bold("Model:")} ${job.model}
1789
+ `);
1790
+ process.stdout.write(` ${c.bold("Status:")} ${job.status === "succeeded" ? c.green(job.status) : job.status === "processing" ? c.yellow(job.status) : c.red(job.status)}
1791
+ `);
1792
+ process.stdout.write(` ${c.bold("Request ID:")} ${job.request_id}
1793
+ `);
1794
+ if (job.output && job.output.length > 0) {
1795
+ process.stdout.write(`
1796
+ ${c.bold("Outputs:")}
1797
+ `);
1798
+ for (const item of job.output) {
1799
+ if (item.url) {
1800
+ process.stdout.write(` [${item.type.toUpperCase()}] ${c.cyan(c.underline(item.url))}
1801
+ `);
1802
+ if (outputFile) {
1803
+ const dlSpinner = new Spinner(`Downloading output to ${outputFile}...`).start();
1804
+ try {
1805
+ await client.downloadOutput(item.url, import_node_path4.default.resolve(process.cwd(), outputFile));
1806
+ dlSpinner.succeed(`Saved to ${c.bold(outputFile)}`);
1807
+ } catch (dlErr) {
1808
+ dlSpinner.fail(`Failed to download: ${dlErr instanceof Error ? dlErr.message : dlErr}`);
1809
+ }
1810
+ }
1811
+ } else if (item.text) {
1812
+ process.stdout.write(`
1813
+ ${item.text}
1814
+ `);
1815
+ }
1816
+ }
1817
+ }
1818
+ process.stdout.write("\n");
1819
+ }
1820
+
1821
+ // src/cli/index.ts
1822
+ var CLI_VERSION = "1.0.0";
1823
+ function printHelp() {
1824
+ process.stdout.write(`
1825
+ ${c.bold(c.cyan("Tein AI CLI"))} - Official Command-Line Interface for Tein AI models
1826
+
1827
+ ${c.bold("USAGE:")}
1828
+ ${c.cyan("tein")} <command> [arguments] [options]
1829
+
1830
+ ${c.bold("COMMANDS:")}
1831
+ ${c.bold("auth")} <login|set-key|logout|status> Manage Tein API key and authentication
1832
+ ${c.bold("models")} [category] List all 25+ models (Video, Image, Audio, Text, 3D)
1833
+ ${c.bold("info")} <model-slug> View parameter schemas, pricing, and options
1834
+ ${c.bold("run")} <model-slug> [options] Generate video, image, audio, or reasoning outputs
1835
+ ${c.bold("status")} <request-id> [options] Check generation progress and retrieve output files
1836
+ ${c.bold("mcp")} Launch stdio MCP server for Claude Desktop / Code
1837
+
1838
+ ${c.bold("GENERATION OPTIONS (for `tein run`):")}
1839
+ ${c.cyan("-p, --prompt")} <text> Text prompt for generation ${c.red("(required)")}
1840
+ ${c.cyan("-o, --output")} <filepath> Download output file locally (e.g. video.mp4, image.png)
1841
+ ${c.cyan("--aspect-ratio")} <ratio> Aspect ratio (16:9, 9:16, 1:1, 4:3, 21:9)
1842
+ ${c.cyan("-d, --duration")} <seconds> Video duration in seconds (4-30s depending on model)
1843
+ ${c.cyan("-r, --resolution")} <res> Resolution (720p, 1080p, 1K, 2K, 4K)
1844
+ ${c.cyan("-q, --quality")} <quality> Quality tier (standard, fast, mini, high)
1845
+ ${c.cyan("-s, --speaker")} <speaker_id> Voice speaker ID for audio TTS models
1846
+ ${c.cyan("--image")} <url> Input image URL for image-to-video or image-to-image
1847
+ ${c.cyan("--generate-audio")} Enable native synchronized audio (Seedance 2.5)
1848
+ ${c.cyan("--async, --no-wait")} Queue task and return request ID immediately
1849
+ ${c.cyan("--json")} Output response in raw JSON format
1850
+ ${c.cyan("--api-key")} <key> Override API key for this request
1851
+
1852
+ ${c.bold("EXAMPLES:")}
1853
+ ${c.dim("# 1. Authenticate with your Tein API key")}
1854
+ ${c.cyan("tein auth login")}
1855
+
1856
+ ${c.dim("# 2. List all Video models")}
1857
+ ${c.cyan("tein models video")}
1858
+
1859
+ ${c.dim("# 3. Generate a 16:9 cinematic image and download it")}
1860
+ ${c.cyan('tein run gpt-image-2 --prompt "Neon Tokyo street in rain" --aspect-ratio 16:9 -o tokyo.png')}
1861
+
1862
+ ${c.dim("# 4. Generate a video using Seedance 2.5 with audio")}
1863
+ ${c.cyan('tein run seedance-2-5 --prompt "Drone shot of ocean waves at sunrise" --duration 5 -o ocean.mp4')}
1864
+
1865
+ ${c.dim("# 5. Generate speech with Seed Audio 1.0")}
1866
+ ${c.cyan('tein run seed-audio-1-0 --prompt "Welcome to Tein AI CLI" -o welcome.wav')}
1867
+
1868
+ ${c.dim("# 6. Run deep reasoning task with GPT-6 Astra")}
1869
+ ${c.cyan('tein run gpt-6-astra --prompt "Write a TypeScript binary search tree implementation"')}
1870
+
1871
+ `);
1872
+ }
1873
+ async function main() {
1874
+ const rawArgs = process.argv.slice(2);
1875
+ const parsed = parseCliArgs(rawArgs);
1876
+ if (parsed.flags.has("help") || parsed.options.help || parsed.command === "help") {
1877
+ printHelp();
1878
+ return;
1879
+ }
1880
+ if (parsed.flags.has("version") || parsed.options.version || parsed.command === "version") {
1881
+ process.stdout.write(`tein version ${CLI_VERSION}
1882
+ `);
1883
+ return;
1884
+ }
1885
+ switch (parsed.command) {
1886
+ case "auth": {
1887
+ await authCommand(parsed.subcommand, parsed.options.key);
1888
+ break;
1889
+ }
1890
+ case "models":
1891
+ case "list": {
1892
+ listModelsCommand(parsed.subcommand);
1893
+ break;
1894
+ }
1895
+ case "info":
1896
+ case "show": {
1897
+ if (!parsed.subcommand) {
1898
+ process.stderr.write(`${c.red("Error:")} Missing model slug.
1899
+ `);
1900
+ process.stderr.write(`Usage: ${c.cyan("tein info <model-slug>")}
1901
+ `);
1902
+ process.exit(1);
1903
+ }
1904
+ modelInfoCommand(parsed.subcommand);
1905
+ break;
1906
+ }
1907
+ case "run":
1908
+ case "generate": {
1909
+ const modelSlug = parsed.subcommand || "";
1910
+ await runCommand(modelSlug, parsed.options);
1911
+ break;
1912
+ }
1913
+ case "status": {
1914
+ const reqId = parsed.subcommand || "";
1915
+ await statusCommand(reqId, parsed.options);
1916
+ break;
1917
+ }
1918
+ case "mcp": {
1919
+ const { spawn } = await import("node:child_process");
1920
+ const cliPath = (await import("node:path")).join(__dirname, "../mcp/cli.js");
1921
+ const child = spawn(process.execPath, [cliPath], {
1922
+ stdio: "inherit",
1923
+ env: process.env
1924
+ });
1925
+ child.on("exit", (code) => process.exit(code ?? 0));
1926
+ break;
1927
+ }
1928
+ default: {
1929
+ if (parsed.command && !parsed.command.startsWith("-")) {
1930
+ await runCommand(parsed.command, parsed.options);
1931
+ return;
1932
+ }
1933
+ printHelp();
1934
+ break;
1935
+ }
1936
+ }
1937
+ }
1938
+ main().catch((err) => {
1939
+ process.stderr.write(`
1940
+ ${c.red("Fatal error:")} ${err instanceof Error ? err.message : err}
1941
+ `);
1942
+ process.exit(1);
1943
+ });