jimeng-cli 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.
@@ -0,0 +1,1664 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildRegionInfo,
4
+ config_default,
5
+ generateImageComposition,
6
+ generateImages,
7
+ generateVideo,
8
+ getCredit,
9
+ getLiveModels,
10
+ getTaskResponse,
11
+ getTokenLiveStatus,
12
+ logger_default,
13
+ parseRegionCode,
14
+ receiveCredit,
15
+ session_pool_default,
16
+ waitForTaskResponse
17
+ } from "../chunk-JZY62VNI.js";
18
+
19
+ // src/cli/app.ts
20
+ import process2 from "process";
21
+
22
+ // src/cli/token-commands.ts
23
+ import { constants as fsConstants } from "fs";
24
+ import path from "path";
25
+ import { access, readFile } from "fs/promises";
26
+ import minimist from "minimist";
27
+ function maskToken(token) {
28
+ const n = token.length;
29
+ if (n <= 10) return "***";
30
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
31
+ }
32
+ function formatUnixMs(value) {
33
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return "-";
34
+ return new Date(value).toISOString();
35
+ }
36
+ function printTokenEntriesTable(items) {
37
+ if (items.length === 0) {
38
+ console.log("(empty)");
39
+ return;
40
+ }
41
+ console.log("token region enabled live lastCredit lastCheckedAt failures");
42
+ for (const item of items) {
43
+ if (!item || typeof item !== "object") continue;
44
+ const entry = item;
45
+ const token = typeof entry.token === "string" ? entry.token : "-";
46
+ const region = typeof entry.region === "string" ? entry.region : "-";
47
+ const enabled = typeof entry.enabled === "boolean" ? String(entry.enabled) : "-";
48
+ const live = typeof entry.live === "boolean" ? String(entry.live) : "-";
49
+ const lastCredit = typeof entry.lastCredit === "number" ? String(entry.lastCredit) : "-";
50
+ const lastCheckedAt = formatUnixMs(entry.lastCheckedAt);
51
+ const failures = typeof entry.consecutiveFailures === "number" ? String(entry.consecutiveFailures) : "-";
52
+ console.log(`${token} ${region} ${enabled} ${live} ${lastCredit} ${lastCheckedAt} ${failures}`);
53
+ }
54
+ }
55
+ function buildTokenPoolSnapshot() {
56
+ return {
57
+ summary: session_pool_default.getSummary(),
58
+ items: session_pool_default.getEntries(true)
59
+ };
60
+ }
61
+ async function pathExists(filePath) {
62
+ try {
63
+ await access(filePath, fsConstants.F_OK);
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+ async function readTokensFromFile(filePathArg, deps) {
70
+ const filePath = path.resolve(filePathArg);
71
+ if (!await pathExists(filePath)) {
72
+ deps.fail(`Token file not found: ${filePath}`);
73
+ }
74
+ return (await readFile(filePath, "utf8")).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
75
+ }
76
+ async function collectTokensFromArgs(args, usage, deps, required = false) {
77
+ const tokens = [...deps.toStringList(args.token)];
78
+ const tokenFile = deps.getSingleString(args, "token-file");
79
+ if (tokenFile) {
80
+ tokens.push(...await readTokensFromFile(tokenFile, deps));
81
+ }
82
+ const deduped = Array.from(new Set(tokens));
83
+ if (required && deduped.length === 0) {
84
+ deps.fail(`No tokens provided.
85
+
86
+ ${usage}`);
87
+ }
88
+ return deduped;
89
+ }
90
+ function createTokenSubcommands(deps) {
91
+ const handleTokenCheck = async (argv) => {
92
+ const args = minimist(argv, {
93
+ string: ["token", "token-file", "region"],
94
+ boolean: ["help", "json"]
95
+ });
96
+ const usage = deps.getUsage("check");
97
+ if (args.help) {
98
+ console.log(usage);
99
+ return;
100
+ }
101
+ const region = deps.getRegionWithDefault(args);
102
+ const regionCode = deps.parseRegionOrFail(region);
103
+ if (!regionCode) {
104
+ deps.fail("Missing region. Use --region cn/us/hk/jp/sg.");
105
+ }
106
+ const tokens = await collectTokensFromArgs(args, usage, deps, true);
107
+ if (!args.json) {
108
+ console.log(`Checking ${tokens.length} token(s)`);
109
+ }
110
+ await deps.ensureTokenPoolReady();
111
+ let invalid = 0;
112
+ let requestErrors = 0;
113
+ const results = [];
114
+ for (const token of tokens) {
115
+ const masked = maskToken(token);
116
+ try {
117
+ const live = await getTokenLiveStatus(token, buildRegionInfo(regionCode));
118
+ await session_pool_default.syncTokenCheckResult(token, live);
119
+ if (live === true) {
120
+ if (!args.json) console.log(`[OK] ${masked} live=true`);
121
+ } else {
122
+ invalid += 1;
123
+ if (!args.json) console.log(`[FAIL] ${masked} live=false`);
124
+ }
125
+ results.push({ token_masked: masked, live: live === true });
126
+ } catch (error) {
127
+ requestErrors += 1;
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ if (!args.json) console.log(`[ERROR] ${masked} ${message}`);
130
+ results.push({ token_masked: masked, error: message });
131
+ }
132
+ }
133
+ if (args.json) {
134
+ deps.printCommandJson("token.check", results, {
135
+ total: tokens.length,
136
+ invalid,
137
+ request_errors: requestErrors
138
+ });
139
+ } else {
140
+ console.log(`Summary: total=${tokens.length} invalid=${invalid} request_errors=${requestErrors}`);
141
+ }
142
+ if (requestErrors > 0) process.exit(3);
143
+ if (invalid > 0) process.exit(2);
144
+ };
145
+ const handleTokenList = async (argv) => {
146
+ const args = minimist(argv, {
147
+ boolean: ["help", "json"]
148
+ });
149
+ const usage = deps.getUsage("list");
150
+ if (args.help) {
151
+ console.log(usage);
152
+ return;
153
+ }
154
+ await deps.ensureTokenPoolReady();
155
+ const normalized = buildTokenPoolSnapshot();
156
+ if (args.json) {
157
+ deps.printCommandJson("token.list", normalized);
158
+ return;
159
+ }
160
+ const body = normalized && typeof normalized === "object" ? normalized : {};
161
+ const summary = body.summary;
162
+ if (summary && typeof summary === "object") {
163
+ console.log("Summary:");
164
+ deps.printJson(summary);
165
+ }
166
+ const items = Array.isArray(body.items) ? body.items : [];
167
+ console.log("Entries:");
168
+ printTokenEntriesTable(items);
169
+ };
170
+ const handleTokenPointsOrReceive = async (argv, action) => {
171
+ const args = minimist(argv, {
172
+ string: ["token", "token-file", "region"],
173
+ boolean: ["help", "json"]
174
+ });
175
+ const usage = deps.getUsage(action);
176
+ if (args.help) {
177
+ console.log(usage);
178
+ return;
179
+ }
180
+ const region = deps.getRegionWithDefault(args);
181
+ const regionCode = deps.parseRegionOrFail(region);
182
+ await deps.ensureTokenPoolReady();
183
+ const tokens = await collectTokensFromArgs(args, usage, deps, false);
184
+ const resolvedTokens = tokens.length > 0 ? tokens.map((token) => {
185
+ var _a;
186
+ const entryRegion = (_a = session_pool_default.getTokenEntry(token)) == null ? void 0 : _a.region;
187
+ const finalRegion = regionCode || entryRegion;
188
+ if (!finalRegion) {
189
+ deps.fail(`Missing region for token ${maskToken(token)}. Provide --region or register token region in token-pool.`);
190
+ }
191
+ return { token, region: finalRegion };
192
+ }) : session_pool_default.getEntries(false).filter((item) => item.enabled && item.live !== false && item.region).filter((item) => regionCode ? item.region === regionCode : true).map((item) => ({ token: item.token, region: item.region }));
193
+ if (resolvedTokens.length === 0) {
194
+ deps.fail("No token available. Provide --token or configure token-pool.");
195
+ }
196
+ const payload = action === "points" ? await Promise.all(
197
+ resolvedTokens.map(async (item) => ({
198
+ token: item.token,
199
+ points: await getCredit(item.token, buildRegionInfo(item.region))
200
+ }))
201
+ ) : await Promise.all(
202
+ resolvedTokens.map(async (item) => {
203
+ const currentCredit = await getCredit(item.token, buildRegionInfo(item.region));
204
+ if (currentCredit.totalCredit <= 0) {
205
+ try {
206
+ await receiveCredit(item.token, buildRegionInfo(item.region));
207
+ const updatedCredit = await getCredit(item.token, buildRegionInfo(item.region));
208
+ return { token: item.token, credits: updatedCredit, received: true };
209
+ } catch (error) {
210
+ return {
211
+ token: item.token,
212
+ credits: currentCredit,
213
+ received: false,
214
+ error: (error == null ? void 0 : error.message) || String(error)
215
+ };
216
+ }
217
+ }
218
+ return { token: item.token, credits: currentCredit, received: false };
219
+ })
220
+ );
221
+ if (args.json) {
222
+ deps.printCommandJson(`token.${action}`, payload);
223
+ return;
224
+ }
225
+ deps.printJson(payload);
226
+ };
227
+ const handleTokenAddOrRemove = async (argv, action) => {
228
+ const args = minimist(argv, {
229
+ string: ["token", "token-file", "region"],
230
+ boolean: ["help", "json"]
231
+ });
232
+ const usage = deps.getUsage(action);
233
+ if (args.help) {
234
+ console.log(usage);
235
+ return;
236
+ }
237
+ const region = deps.getRegionWithDefault(args);
238
+ await deps.ensureTokenPoolReady();
239
+ const tokens = await collectTokensFromArgs(args, usage, deps, true);
240
+ const regionCode = deps.parseRegionOrFail(region);
241
+ const payload = action === "add" ? {
242
+ ...await session_pool_default.addTokens(tokens, { defaultRegion: regionCode || void 0 }),
243
+ summary: session_pool_default.getSummary()
244
+ } : {
245
+ ...await session_pool_default.removeTokens(tokens),
246
+ summary: session_pool_default.getSummary()
247
+ };
248
+ if (args.json) {
249
+ deps.printCommandJson(`token.${action}`, deps.unwrapBody(payload), { region });
250
+ return;
251
+ }
252
+ deps.printJson(deps.unwrapBody(payload));
253
+ };
254
+ const handleTokenEnableOrDisable = async (argv, action) => {
255
+ const args = minimist(argv, {
256
+ string: ["token"],
257
+ boolean: ["help", "json"]
258
+ });
259
+ const usage = deps.getUsage(action);
260
+ if (args.help) {
261
+ console.log(usage);
262
+ return;
263
+ }
264
+ const token = deps.getSingleString(args, "token");
265
+ if (!token) {
266
+ deps.failWithUsage("Missing required --token.", usage);
267
+ }
268
+ await deps.ensureTokenPoolReady();
269
+ const payload = {
270
+ updated: await session_pool_default.setTokenEnabled(token, action === "enable"),
271
+ summary: session_pool_default.getSummary()
272
+ };
273
+ if (args.json) {
274
+ deps.printCommandJson(`token.${action}`, deps.unwrapBody(payload));
275
+ return;
276
+ }
277
+ deps.printJson(deps.unwrapBody(payload));
278
+ };
279
+ const handleTokenPool = async (argv) => {
280
+ const args = minimist(argv, {
281
+ boolean: ["help", "json"]
282
+ });
283
+ const usage = deps.getUsage("pool");
284
+ if (args.help) {
285
+ console.log(usage);
286
+ return;
287
+ }
288
+ await deps.ensureTokenPoolReady();
289
+ const normalized = buildTokenPoolSnapshot();
290
+ if (args.json) {
291
+ deps.printCommandJson("token.pool", normalized);
292
+ return;
293
+ }
294
+ const body = normalized && typeof normalized === "object" ? normalized : {};
295
+ console.log("Summary:");
296
+ deps.printJson(body.summary ?? {});
297
+ console.log("Entries:");
298
+ printTokenEntriesTable(Array.isArray(body.items) ? body.items : []);
299
+ };
300
+ const handleTokenPoolCheckOrReload = async (argv, action) => {
301
+ const args = minimist(argv, {
302
+ boolean: ["help", "json"]
303
+ });
304
+ const usage = deps.getUsage(action);
305
+ if (args.help) {
306
+ console.log(usage);
307
+ return;
308
+ }
309
+ await deps.ensureTokenPoolReady();
310
+ const payload = action === "pool-check" ? {
311
+ ...await session_pool_default.runHealthCheck(),
312
+ summary: session_pool_default.getSummary()
313
+ } : (await session_pool_default.reloadFromDisk(), {
314
+ reloaded: true,
315
+ summary: session_pool_default.getSummary(),
316
+ items: buildTokenPoolSnapshot().items
317
+ });
318
+ if (args.json) {
319
+ deps.printCommandJson(`token.${action}`, deps.unwrapBody(payload));
320
+ return;
321
+ }
322
+ deps.printJson(deps.unwrapBody(payload));
323
+ };
324
+ return [
325
+ {
326
+ name: "list",
327
+ description: "List token pool entries",
328
+ usageLine: " jimeng token list [options]",
329
+ options: [deps.jsonOption, deps.helpOption],
330
+ handler: handleTokenList
331
+ },
332
+ {
333
+ name: "check",
334
+ description: "Validate tokens directly",
335
+ usageLine: " jimeng token check --token <token> [--token <token> ...] [options]",
336
+ options: [
337
+ " --token <token> Token, can be repeated",
338
+ " --token-file <path> Read tokens from file (one per line, # for comments)",
339
+ " --region <region> X-Region, default cn (cn/us/hk/jp/sg)",
340
+ deps.jsonOption,
341
+ deps.helpOption
342
+ ],
343
+ handler: handleTokenCheck
344
+ },
345
+ {
346
+ name: "points",
347
+ description: "Query token points directly",
348
+ usageLine: " jimeng token points [options]",
349
+ options: [
350
+ " --token <token> Token, can be repeated",
351
+ " --token-file <path> Read tokens from file (one per line, # for comments)",
352
+ " --region <region> Filter tokens by X-Region, default cn (cn/us/hk/jp/sg)",
353
+ deps.jsonOption,
354
+ deps.helpOption
355
+ ],
356
+ handler: async (argv) => handleTokenPointsOrReceive(argv, "points")
357
+ },
358
+ {
359
+ name: "receive",
360
+ description: "Receive token credits directly",
361
+ usageLine: " jimeng token receive [options]",
362
+ options: [
363
+ " --token <token> Token, can be repeated",
364
+ " --token-file <path> Read tokens from file (one per line, # for comments)",
365
+ " --region <region> Filter tokens by X-Region, default cn (cn/us/hk/jp/sg)",
366
+ deps.jsonOption,
367
+ deps.helpOption
368
+ ],
369
+ handler: async (argv) => handleTokenPointsOrReceive(argv, "receive")
370
+ },
371
+ {
372
+ name: "add",
373
+ description: "Add token(s) into token-pool",
374
+ usageLine: " jimeng token add --token <token> [--token <token> ...] [options]",
375
+ options: [
376
+ " --token <token> Token, can be repeated",
377
+ " --token-file <path> Read tokens from file (one per line, # for comments)",
378
+ " --region <region> Region for add, default cn (cn/us/hk/jp/sg)",
379
+ deps.jsonOption,
380
+ deps.helpOption
381
+ ],
382
+ handler: async (argv) => handleTokenAddOrRemove(argv, "add")
383
+ },
384
+ {
385
+ name: "remove",
386
+ description: "Remove token(s) from token-pool",
387
+ usageLine: " jimeng token remove --token <token> [--token <token> ...] [options]",
388
+ options: [
389
+ " --token <token> Token, can be repeated",
390
+ " --token-file <path> Read tokens from file (one per line, # for comments)",
391
+ deps.jsonOption,
392
+ deps.helpOption
393
+ ],
394
+ handler: async (argv) => handleTokenAddOrRemove(argv, "remove")
395
+ },
396
+ {
397
+ name: "enable",
398
+ description: "Enable one token in token-pool",
399
+ usageLine: " jimeng token enable --token <token> [options]",
400
+ options: [" --token <token> Required, a single token", deps.jsonOption, deps.helpOption],
401
+ handler: async (argv) => handleTokenEnableOrDisable(argv, "enable")
402
+ },
403
+ {
404
+ name: "disable",
405
+ description: "Disable one token in token-pool",
406
+ usageLine: " jimeng token disable --token <token> [options]",
407
+ options: [" --token <token> Required, a single token", deps.jsonOption, deps.helpOption],
408
+ handler: async (argv) => handleTokenEnableOrDisable(argv, "disable")
409
+ },
410
+ {
411
+ name: "pool",
412
+ description: "Show token-pool summary and entries",
413
+ usageLine: " jimeng token pool [options]",
414
+ options: [deps.jsonOption, deps.helpOption],
415
+ handler: handleTokenPool
416
+ },
417
+ {
418
+ name: "pool-check",
419
+ description: "Trigger token-pool health check",
420
+ usageLine: " jimeng token pool-check [options]",
421
+ options: [deps.jsonOption, deps.helpOption],
422
+ handler: async (argv) => handleTokenPoolCheckOrReload(argv, "pool-check")
423
+ },
424
+ {
425
+ name: "pool-reload",
426
+ description: "Reload token-pool from disk",
427
+ usageLine: " jimeng token pool-reload [options]",
428
+ options: [deps.jsonOption, deps.helpOption],
429
+ handler: async (argv) => handleTokenPoolCheckOrReload(argv, "pool-reload")
430
+ }
431
+ ];
432
+ }
433
+
434
+ // src/cli/query-commands.ts
435
+ import minimist2 from "minimist";
436
+ function parseTaskTypeOrFail(value, deps) {
437
+ if (!value) return void 0;
438
+ if (value === "image" || value === "video") return value;
439
+ deps.fail(`Invalid --type: ${value}. Use image or video.`);
440
+ }
441
+ function parseResponseFormatOrFail(value, deps) {
442
+ if (!value) return "url";
443
+ if (value === "url" || value === "b64_json") return value;
444
+ deps.fail(`Invalid --response-format: ${value}. Use url or b64_json.`);
445
+ }
446
+ function parsePositiveNumberOption(args, key, deps) {
447
+ const raw = deps.getSingleString(args, key);
448
+ if (!raw) return void 0;
449
+ const parsed = Number(raw);
450
+ if (!Number.isFinite(parsed) || parsed <= 0) {
451
+ deps.fail(`Invalid --${key}: ${raw}`);
452
+ }
453
+ return parsed;
454
+ }
455
+ var TASK_STATUS_TEXT = {
456
+ 10: "PENDING",
457
+ 20: "PROCESSING",
458
+ 40: "FAILED",
459
+ 50: "COMPLETED"
460
+ };
461
+ function taskStatusText(status) {
462
+ return TASK_STATUS_TEXT[status] || "UNKNOWN";
463
+ }
464
+ function formatUnixSeconds(value) {
465
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return "-";
466
+ return `${value} (${new Date(value * 1e3).toISOString()})`;
467
+ }
468
+ function collectTaskInfo(payload, deps) {
469
+ const normalized = deps.unwrapBody(payload);
470
+ if (!normalized || typeof normalized !== "object") return null;
471
+ const obj = normalized;
472
+ if (typeof obj.task_id !== "string" || obj.task_id.length === 0) return null;
473
+ return {
474
+ task_id: obj.task_id,
475
+ type: typeof obj.type === "string" ? obj.type : void 0,
476
+ status: typeof obj.status === "number" ? obj.status : void 0,
477
+ fail_code: typeof obj.fail_code === "string" || obj.fail_code === null ? obj.fail_code : void 0,
478
+ created: typeof obj.created === "number" ? obj.created : void 0,
479
+ data: obj.data
480
+ };
481
+ }
482
+ function printTaskInfo(task, deps) {
483
+ console.log(`Task ID: ${task.task_id}`);
484
+ if (task.type) console.log(`Type: ${task.type}`);
485
+ if (typeof task.status === "number") {
486
+ console.log(`Status: ${task.status} (${taskStatusText(task.status)})`);
487
+ }
488
+ if (task.fail_code) console.log(`Fail Code: ${task.fail_code}`);
489
+ if (typeof task.created === "number") {
490
+ console.log(`Created: ${formatUnixSeconds(task.created)}`);
491
+ }
492
+ if (task.data != null) {
493
+ console.log("Data:");
494
+ deps.printJson(task.data);
495
+ }
496
+ }
497
+ function createQueryCommandHandlers(deps) {
498
+ const handleModelsList = async (argv) => {
499
+ const args = minimist2(argv, {
500
+ string: ["region", "token"],
501
+ boolean: ["help", "json", "verbose"]
502
+ });
503
+ if (args.help) {
504
+ console.log(deps.usageModelsList());
505
+ return;
506
+ }
507
+ const region = deps.getRegionWithDefault(args);
508
+ const parsedRegion = deps.parseRegionOrFail(region);
509
+ const token = deps.getSingleString(args, "token");
510
+ await deps.ensureTokenPoolReady();
511
+ const auth = token ? `Bearer ${token}` : void 0;
512
+ const direct = await getLiveModels(auth, parsedRegion || region);
513
+ const normalized = { object: "list", data: direct.data };
514
+ if (args.json) {
515
+ deps.printCommandJson("models.list", normalized, { region: region || null });
516
+ return;
517
+ }
518
+ const data = normalized && typeof normalized === "object" && Array.isArray(normalized.data) ? normalized.data : [];
519
+ if (data.length === 0) {
520
+ deps.fail(`No models found in response: ${JSON.stringify(normalized)}`);
521
+ }
522
+ if (args.verbose) {
523
+ console.log("id type desc capabilities");
524
+ for (const item of data) {
525
+ if (!item || typeof item !== "object") continue;
526
+ const model = item;
527
+ const id = typeof model.id === "string" ? model.id : "";
528
+ if (!id) continue;
529
+ const modelType = typeof model.model_type === "string" ? model.model_type : "-";
530
+ const description = typeof model.description === "string" ? model.description : "-";
531
+ const capabilities = Array.isArray(model.capabilities) ? model.capabilities.filter((cap) => typeof cap === "string").join(",") : "-";
532
+ console.log(`${id} type=${modelType} desc=${description} capabilities=${capabilities}`);
533
+ }
534
+ return;
535
+ }
536
+ for (const item of data) {
537
+ if (!item || typeof item !== "object") continue;
538
+ const id = item.id;
539
+ if (typeof id === "string" && id.length > 0) {
540
+ console.log(id);
541
+ }
542
+ }
543
+ };
544
+ const handleTaskGet = async (argv) => {
545
+ const args = minimist2(argv, {
546
+ string: ["token", "region", "task-id", "type", "response-format"],
547
+ boolean: ["help", "json"]
548
+ });
549
+ if (args.help) {
550
+ console.log(deps.usageTaskGet());
551
+ return;
552
+ }
553
+ const taskId = deps.getSingleString(args, "task-id");
554
+ if (!taskId) deps.fail(`Missing required --task-id.
555
+
556
+ ${deps.usageTaskGet()}`);
557
+ const type = parseTaskTypeOrFail(deps.getSingleString(args, "type"), deps);
558
+ const responseFormat = parseResponseFormatOrFail(deps.getSingleString(args, "response-format"), deps);
559
+ const token = deps.getSingleString(args, "token");
560
+ const region = deps.getRegionWithDefault(args);
561
+ const isJson = Boolean(args.json);
562
+ const pick = await deps.pickDirectTokenForTask(token, region);
563
+ const normalized = await getTaskResponse(
564
+ taskId,
565
+ pick.token,
566
+ buildRegionInfo(pick.region),
567
+ {
568
+ type,
569
+ responseFormat
570
+ }
571
+ );
572
+ const taskInfo = collectTaskInfo(normalized, deps);
573
+ if (!taskInfo) {
574
+ if (isJson) {
575
+ deps.printCommandJson("task.get", deps.unwrapBody(normalized));
576
+ } else {
577
+ deps.printJson(deps.unwrapBody(normalized));
578
+ }
579
+ return;
580
+ }
581
+ if (isJson) deps.printCommandJson("task.get", taskInfo);
582
+ else printTaskInfo(taskInfo, deps);
583
+ };
584
+ const handleTaskWait = async (argv) => {
585
+ const args = minimist2(argv, {
586
+ string: [
587
+ "token",
588
+ "region",
589
+ "task-id",
590
+ "type",
591
+ "response-format",
592
+ "wait-timeout-seconds",
593
+ "poll-interval-ms"
594
+ ],
595
+ boolean: ["help", "json"]
596
+ });
597
+ if (args.help) {
598
+ console.log(deps.usageTaskWait());
599
+ return;
600
+ }
601
+ const taskId = deps.getSingleString(args, "task-id");
602
+ if (!taskId) deps.fail(`Missing required --task-id.
603
+
604
+ ${deps.usageTaskWait()}`);
605
+ const token = deps.getSingleString(args, "token");
606
+ const region = deps.getRegionWithDefault(args);
607
+ const isJson = Boolean(args.json);
608
+ const body = {};
609
+ const type = parseTaskTypeOrFail(deps.getSingleString(args, "type"), deps);
610
+ const responseFormat = parseResponseFormatOrFail(deps.getSingleString(args, "response-format"), deps);
611
+ if (type) body.type = type;
612
+ body.response_format = responseFormat;
613
+ const waitTimeoutSeconds = parsePositiveNumberOption(args, "wait-timeout-seconds", deps);
614
+ if (waitTimeoutSeconds !== void 0) body.wait_timeout_seconds = waitTimeoutSeconds;
615
+ const pollIntervalMs = parsePositiveNumberOption(args, "poll-interval-ms", deps);
616
+ if (pollIntervalMs !== void 0) body.poll_interval_ms = pollIntervalMs;
617
+ const pick = await deps.pickDirectTokenForTask(token, region);
618
+ const normalized = await waitForTaskResponse(
619
+ taskId,
620
+ pick.token,
621
+ buildRegionInfo(pick.region),
622
+ {
623
+ type,
624
+ responseFormat,
625
+ waitTimeoutSeconds: typeof body.wait_timeout_seconds === "number" ? body.wait_timeout_seconds : void 0,
626
+ pollIntervalMs: typeof body.poll_interval_ms === "number" ? body.poll_interval_ms : void 0
627
+ }
628
+ );
629
+ const taskInfo = collectTaskInfo(normalized, deps);
630
+ if (!taskInfo) {
631
+ if (isJson) {
632
+ deps.printCommandJson("task.wait", deps.unwrapBody(normalized));
633
+ } else {
634
+ deps.printJson(deps.unwrapBody(normalized));
635
+ }
636
+ return;
637
+ }
638
+ if (isJson) deps.printCommandJson("task.wait", taskInfo);
639
+ else printTaskInfo(taskInfo, deps);
640
+ };
641
+ return {
642
+ handleModelsList,
643
+ handleTaskGet,
644
+ handleTaskWait,
645
+ printTaskInfo: (task) => {
646
+ const normalized = collectTaskInfo(task, deps);
647
+ if (!normalized) {
648
+ deps.printJson(task);
649
+ return;
650
+ }
651
+ printTaskInfo(normalized, deps);
652
+ }
653
+ };
654
+ }
655
+
656
+ // src/cli/media-commands.ts
657
+ import { constants as fsConstants2 } from "fs";
658
+ import { access as access2, mkdir, readFile as readFile2, writeFile } from "fs/promises";
659
+ import path3 from "path";
660
+ import minimist3 from "minimist";
661
+
662
+ // src/cli/video-input.ts
663
+ import path2 from "path";
664
+ var VIDEO_OMNI_IMAGE_SLOT_KEYS = Array.from({ length: 9 }, (_, i) => `image-file-${i + 1}`);
665
+ var VIDEO_OMNI_VIDEO_SLOT_KEYS = Array.from({ length: 3 }, (_, i) => `video-file-${i + 1}`);
666
+ var VIDEO_SUPPORTED_MODES = [
667
+ "text_to_video",
668
+ "image_to_video",
669
+ "first_last_frames",
670
+ "omni_reference"
671
+ ];
672
+ var VIDEO_OMNI_SUPPORTED_MODELS = /* @__PURE__ */ new Set(["jimeng-video-seedance-2.0", "jimeng-video-seedance-2.0-fast"]);
673
+ function parseVideoCliMode(args, usage, deps) {
674
+ const cliModeRaw = deps.getSingleString(args, "mode") || "text_to_video";
675
+ if (!VIDEO_SUPPORTED_MODES.includes(cliModeRaw)) {
676
+ deps.failWithUsage(
677
+ `Invalid --mode: ${cliModeRaw}. Use text_to_video, image_to_video, first_last_frames, or omni_reference.`,
678
+ usage
679
+ );
680
+ }
681
+ return cliModeRaw;
682
+ }
683
+ function collectVideoInputPlan(args, usage, deps) {
684
+ const repeatedImageInputs = deps.toStringList(args["image-file"]);
685
+ const repeatedVideoInputs = deps.toStringList(args["video-file"]);
686
+ const explicitImageSlots = VIDEO_OMNI_IMAGE_SLOT_KEYS.map((key, i) => ({ slot: i + 1, input: deps.getSingleString(args, key) })).filter((item) => Boolean(item.input));
687
+ const explicitVideoSlots = VIDEO_OMNI_VIDEO_SLOT_KEYS.map((key, i) => ({ slot: i + 1, input: deps.getSingleString(args, key) })).filter((item) => Boolean(item.input));
688
+ if (repeatedImageInputs.length > 0 && explicitImageSlots.length > 0) {
689
+ deps.failWithUsage(
690
+ "Do not mix repeated --image-file with explicit --image-file-N in one command.",
691
+ usage
692
+ );
693
+ }
694
+ if (repeatedVideoInputs.length > 0 && explicitVideoSlots.length > 0) {
695
+ deps.failWithUsage(
696
+ "Do not mix repeated --video-file with explicit --video-file-N in one command.",
697
+ usage
698
+ );
699
+ }
700
+ return {
701
+ repeatedImageInputs,
702
+ repeatedVideoInputs,
703
+ explicitImageSlots,
704
+ explicitVideoSlots,
705
+ totalImageInputs: repeatedImageInputs.length + explicitImageSlots.length,
706
+ totalVideoInputs: repeatedVideoInputs.length + explicitVideoSlots.length
707
+ };
708
+ }
709
+ function validateVideoModeAndModel(cliMode, model, plan, usage, deps) {
710
+ if (cliMode === "omni_reference" && !VIDEO_OMNI_SUPPORTED_MODELS.has(model)) {
711
+ deps.failWithUsage(
712
+ `omni_reference mode requires --model jimeng-video-seedance-2.0 or jimeng-video-seedance-2.0-fast (current: ${model}).`,
713
+ usage
714
+ );
715
+ }
716
+ if (cliMode === "text_to_video") {
717
+ if (plan.totalImageInputs + plan.totalVideoInputs > 0) {
718
+ deps.failWithUsage("text_to_video mode does not accept --image-file or --video-file inputs.", usage);
719
+ }
720
+ return;
721
+ }
722
+ if (cliMode === "image_to_video") {
723
+ if (plan.totalVideoInputs > 0) {
724
+ deps.failWithUsage("image_to_video mode does not accept --video-file.", usage);
725
+ }
726
+ if (plan.totalImageInputs !== 1) {
727
+ deps.failWithUsage("image_to_video mode requires exactly one --image-file input.", usage);
728
+ }
729
+ return;
730
+ }
731
+ if (cliMode === "first_last_frames") {
732
+ if (plan.totalVideoInputs > 0) {
733
+ deps.failWithUsage("first_last_frames mode does not accept --video-file.", usage);
734
+ }
735
+ if (plan.totalImageInputs === 0) {
736
+ deps.failWithUsage("first_last_frames mode requires at least one --image-file input.", usage);
737
+ }
738
+ if (plan.totalImageInputs > 2) {
739
+ deps.failWithUsage("first_last_frames mode supports at most 2 image inputs.", usage);
740
+ }
741
+ return;
742
+ }
743
+ if (plan.totalImageInputs + plan.totalVideoInputs === 0) {
744
+ deps.failWithUsage("omni_reference mode requires at least one --image-file or --video-file input.", usage);
745
+ }
746
+ if (plan.totalImageInputs > 9) {
747
+ deps.failWithUsage("omni_reference supports at most 9 image inputs.", usage);
748
+ }
749
+ if (plan.totalVideoInputs > 3) {
750
+ deps.failWithUsage("omni_reference supports at most 3 video inputs.", usage);
751
+ }
752
+ }
753
+ async function buildDirectVideoInputPayload(cliMode, plan, deps) {
754
+ const payload = {
755
+ filePaths: [],
756
+ files: {},
757
+ httpRequest: { body: {} }
758
+ };
759
+ const registerInput = async (fieldName, input, mediaType) => {
760
+ if (deps.isHttpUrl(input)) {
761
+ if (cliMode === "omni_reference") {
762
+ payload.httpRequest.body[fieldName] = input;
763
+ } else if (mediaType === "image") {
764
+ payload.filePaths.push(input);
765
+ } else {
766
+ deps.fail(`Mode ${cliMode} does not support video URL input.`);
767
+ }
768
+ return;
769
+ }
770
+ const filePath = path2.resolve(input);
771
+ if (!await deps.pathExists(filePath)) {
772
+ deps.fail(`Input file not found for ${fieldName}: ${filePath}`);
773
+ }
774
+ payload.files[fieldName] = {
775
+ filepath: filePath,
776
+ originalFilename: path2.basename(filePath)
777
+ };
778
+ };
779
+ if (cliMode === "omni_reference") {
780
+ for (let i = 0; i < plan.repeatedImageInputs.length; i += 1) {
781
+ await registerInput(`image_file_${i + 1}`, plan.repeatedImageInputs[i], "image");
782
+ }
783
+ for (let i = 0; i < plan.repeatedVideoInputs.length; i += 1) {
784
+ await registerInput(`video_file_${i + 1}`, plan.repeatedVideoInputs[i], "video");
785
+ }
786
+ for (const slot of plan.explicitImageSlots) {
787
+ await registerInput(`image_file_${slot.slot}`, slot.input, "image");
788
+ }
789
+ for (const slot of plan.explicitVideoSlots) {
790
+ await registerInput(`video_file_${slot.slot}`, slot.input, "video");
791
+ }
792
+ return payload;
793
+ }
794
+ const imageInputs = plan.repeatedImageInputs.length > 0 ? plan.repeatedImageInputs : plan.explicitImageSlots.sort((a, b) => a.slot - b.slot).map((item) => item.input);
795
+ for (let i = 0; i < imageInputs.length; i += 1) {
796
+ await registerInput(`image_file_${i + 1}`, imageInputs[i], "image");
797
+ }
798
+ return payload;
799
+ }
800
+
801
+ // src/cli/media-commands.ts
802
+ function ensurePrompt(prompt, usage, deps) {
803
+ if (!prompt) {
804
+ deps.fail(`Missing required --prompt.
805
+
806
+ ${usage}`);
807
+ }
808
+ return prompt;
809
+ }
810
+ function isHttpUrl(input) {
811
+ return /^https?:\/\//i.test(input);
812
+ }
813
+ async function pathExists2(filePath) {
814
+ try {
815
+ await access2(filePath, fsConstants2.F_OK);
816
+ return true;
817
+ } catch {
818
+ return false;
819
+ }
820
+ }
821
+ function detectImageExtension(contentType) {
822
+ if (!contentType) return null;
823
+ if (contentType.includes("image/jpeg")) return "jpg";
824
+ if (contentType.includes("image/png")) return "png";
825
+ if (contentType.includes("image/webp")) return "webp";
826
+ if (contentType.includes("image/gif")) return "gif";
827
+ return null;
828
+ }
829
+ function detectImageExtensionFromUrl(fileUrl) {
830
+ try {
831
+ const pathname = new URL(fileUrl).pathname.toLowerCase();
832
+ if (pathname.endsWith(".jpg") || pathname.endsWith(".jpeg")) return "jpg";
833
+ if (pathname.endsWith(".png")) return "png";
834
+ if (pathname.endsWith(".webp")) return "webp";
835
+ if (pathname.endsWith(".gif")) return "gif";
836
+ } catch {
837
+ return null;
838
+ }
839
+ return null;
840
+ }
841
+ function detectImageExtensionFromBuffer(buffer) {
842
+ if (buffer.length >= 8) {
843
+ if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) return "png";
844
+ }
845
+ if (buffer.length >= 3) {
846
+ if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) return "jpg";
847
+ }
848
+ if (buffer.length >= 12) {
849
+ if (buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WEBP") return "webp";
850
+ }
851
+ if (buffer.length >= 6) {
852
+ const sig = buffer.toString("ascii", 0, 6);
853
+ if (sig === "GIF87a" || sig === "GIF89a") return "gif";
854
+ }
855
+ return null;
856
+ }
857
+ function detectVideoExtension(contentType, fileUrl) {
858
+ if (contentType == null ? void 0 : contentType.includes("video/mp4")) return "mp4";
859
+ if (contentType == null ? void 0 : contentType.includes("video/webm")) return "webm";
860
+ const pathname = new URL(fileUrl).pathname.toLowerCase();
861
+ if (pathname.endsWith(".mp4")) return "mp4";
862
+ if (pathname.endsWith(".webm")) return "webm";
863
+ if (pathname.endsWith(".mov")) return "mov";
864
+ return "mp4";
865
+ }
866
+ function parsePositiveNumberOption2(args, key, deps) {
867
+ const raw = deps.getSingleString(args, key);
868
+ if (!raw) return void 0;
869
+ const parsed = Number(raw);
870
+ if (!Number.isFinite(parsed) || parsed <= 0) {
871
+ deps.fail(`Invalid --${key}: ${raw}`);
872
+ }
873
+ return parsed;
874
+ }
875
+ function applyWaitOptionsToBody(body, args, deps, includeWaitFlag = true) {
876
+ const wait = Boolean(args.wait);
877
+ if (includeWaitFlag) body.wait = wait;
878
+ const waitTimeoutSeconds = parsePositiveNumberOption2(args, "wait-timeout-seconds", deps);
879
+ if (waitTimeoutSeconds !== void 0) body.wait_timeout_seconds = waitTimeoutSeconds;
880
+ const pollIntervalMs = parsePositiveNumberOption2(args, "poll-interval-ms", deps);
881
+ if (pollIntervalMs !== void 0) body.poll_interval_ms = pollIntervalMs;
882
+ return wait;
883
+ }
884
+ async function downloadBinary(url, deps) {
885
+ const response = await fetch(url);
886
+ if (!response.ok) {
887
+ deps.fail(`Download failed (${response.status}): ${url}`);
888
+ }
889
+ return {
890
+ buffer: Buffer.from(await response.arrayBuffer()),
891
+ contentType: response.headers.get("content-type")
892
+ };
893
+ }
894
+ async function downloadImages(urls, outputDir, prefix, deps) {
895
+ const dir = path3.resolve(outputDir);
896
+ await mkdir(dir, { recursive: true });
897
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
898
+ const saved = [];
899
+ for (let i = 0; i < urls.length; i += 1) {
900
+ const imageUrl = urls[i];
901
+ const { buffer, contentType } = await downloadBinary(imageUrl, deps);
902
+ const ext = detectImageExtension(contentType) ?? detectImageExtensionFromBuffer(buffer) ?? detectImageExtensionFromUrl(imageUrl) ?? "png";
903
+ const fileName = `${prefix}-${timestamp}-${String(i + 1).padStart(2, "0")}.${ext}`;
904
+ const filePath = path3.join(dir, fileName);
905
+ await writeFile(filePath, buffer);
906
+ saved.push(filePath);
907
+ }
908
+ return saved;
909
+ }
910
+ function createMediaCommandHandlers(deps) {
911
+ const handleImageGenerate = async (argv) => {
912
+ const args = minimist3(argv, {
913
+ string: [
914
+ "token",
915
+ "region",
916
+ "prompt",
917
+ "model",
918
+ "ratio",
919
+ "resolution",
920
+ "negative-prompt",
921
+ "sample-strength",
922
+ "output-dir",
923
+ "wait-timeout-seconds",
924
+ "poll-interval-ms"
925
+ ],
926
+ boolean: ["help", "intelligent-ratio", "wait", "json"],
927
+ default: { wait: true }
928
+ });
929
+ if (args.help) {
930
+ console.log(deps.usageImageGenerate());
931
+ return;
932
+ }
933
+ const token = deps.getSingleString(args, "token");
934
+ const region = deps.getRegionWithDefault(args);
935
+ const prompt = ensurePrompt(deps.getSingleString(args, "prompt"), deps.usageImageGenerate(), deps);
936
+ const outputDir = deps.getSingleString(args, "output-dir") || "./pic/cli-image-generate";
937
+ const body = {
938
+ prompt,
939
+ model: deps.getSingleString(args, "model") || "jimeng-4.5",
940
+ ratio: deps.getSingleString(args, "ratio") || "1:1",
941
+ resolution: deps.getSingleString(args, "resolution") || "2k"
942
+ };
943
+ const negativePrompt = deps.getSingleString(args, "negative-prompt");
944
+ if (negativePrompt) body.negative_prompt = negativePrompt;
945
+ if (args["intelligent-ratio"]) body.intelligent_ratio = true;
946
+ const wait = applyWaitOptionsToBody(body, args, deps);
947
+ const isJson = Boolean(args.json);
948
+ const sampleStrengthRaw = deps.getSingleString(args, "sample-strength");
949
+ if (sampleStrengthRaw) {
950
+ const parsed = Number(sampleStrengthRaw);
951
+ if (!Number.isFinite(parsed)) {
952
+ deps.fail(`Invalid --sample-strength: ${sampleStrengthRaw}`);
953
+ }
954
+ body.sample_strength = parsed;
955
+ }
956
+ const pick = await deps.pickDirectTokenForGeneration(
957
+ token,
958
+ region,
959
+ String(body.model || "jimeng-4.5"),
960
+ "image"
961
+ );
962
+ const result = await generateImages(
963
+ String(body.model || "jimeng-4.5"),
964
+ String(prompt),
965
+ {
966
+ ratio: String(body.ratio || "1:1"),
967
+ resolution: String(body.resolution || "2k"),
968
+ sampleStrength: typeof body.sample_strength === "number" ? body.sample_strength : void 0,
969
+ negativePrompt: typeof body.negative_prompt === "string" ? body.negative_prompt : void 0,
970
+ intelligentRatio: Boolean(body.intelligent_ratio),
971
+ wait,
972
+ waitTimeoutSeconds: typeof body.wait_timeout_seconds === "number" ? body.wait_timeout_seconds : void 0,
973
+ pollIntervalMs: typeof body.poll_interval_ms === "number" ? body.poll_interval_ms : void 0
974
+ },
975
+ pick.token,
976
+ buildRegionInfo(pick.region)
977
+ );
978
+ if (!Array.isArray(result)) {
979
+ if (isJson) deps.printCommandJson("image.generate", result, { wait });
980
+ else deps.printTaskInfo(result);
981
+ return;
982
+ }
983
+ const urls = result;
984
+ if (urls.length === 0) deps.fail("No image URL found in response.");
985
+ const savedFiles = await downloadImages(urls, outputDir, "jimeng-image-generate", deps);
986
+ if (isJson) {
987
+ deps.printCommandJson(
988
+ "image.generate",
989
+ { data: urls.map((url) => ({ url })), files: savedFiles },
990
+ { wait }
991
+ );
992
+ } else {
993
+ deps.printDownloadSummary("image", savedFiles);
994
+ }
995
+ };
996
+ const handleImageEdit = async (argv) => {
997
+ const args = minimist3(argv, {
998
+ string: [
999
+ "token",
1000
+ "region",
1001
+ "prompt",
1002
+ "image",
1003
+ "model",
1004
+ "ratio",
1005
+ "resolution",
1006
+ "negative-prompt",
1007
+ "sample-strength",
1008
+ "output-dir",
1009
+ "wait-timeout-seconds",
1010
+ "poll-interval-ms"
1011
+ ],
1012
+ boolean: ["help", "intelligent-ratio", "wait", "json"],
1013
+ default: { wait: true }
1014
+ });
1015
+ if (args.help) {
1016
+ console.log(deps.usageImageEdit());
1017
+ return;
1018
+ }
1019
+ const token = deps.getSingleString(args, "token");
1020
+ const region = deps.getRegionWithDefault(args);
1021
+ const prompt = ensurePrompt(deps.getSingleString(args, "prompt"), deps.usageImageEdit(), deps);
1022
+ const sources = deps.toStringList(args.image);
1023
+ if (sources.length === 0) deps.failWithUsage("Missing required --image.", deps.usageImageEdit());
1024
+ if (sources.length > 10) deps.fail("At most 10 images are supported for image edit.");
1025
+ const outputDir = deps.getSingleString(args, "output-dir") || "./pic/cli-image-edit";
1026
+ const model = deps.getSingleString(args, "model") || "jimeng-4.5";
1027
+ const ratio = deps.getSingleString(args, "ratio") || "1:1";
1028
+ const resolution = deps.getSingleString(args, "resolution") || "2k";
1029
+ const negativePrompt = deps.getSingleString(args, "negative-prompt");
1030
+ const sampleStrengthRaw = deps.getSingleString(args, "sample-strength");
1031
+ const intelligentRatio = Boolean(args["intelligent-ratio"]);
1032
+ const wait = Boolean(args.wait);
1033
+ const isJson = Boolean(args.json);
1034
+ const allUrls = sources.every(isHttpUrl);
1035
+ const allLocal = sources.every((item) => !isHttpUrl(item));
1036
+ if (!allUrls && !allLocal) {
1037
+ deps.fail("Mixed image sources are not supported. Use all URLs or all local files.");
1038
+ }
1039
+ const pick = await deps.pickDirectTokenForGeneration(token, region, model, "image");
1040
+ const images = [];
1041
+ if (allUrls) {
1042
+ images.push(...sources);
1043
+ } else {
1044
+ for (const source of sources) {
1045
+ const imagePath = path3.resolve(source);
1046
+ if (!await pathExists2(imagePath)) deps.fail(`Image file not found: ${imagePath}`);
1047
+ images.push(await readFile2(imagePath));
1048
+ }
1049
+ }
1050
+ const sampleStrength = sampleStrengthRaw ? Number(sampleStrengthRaw) : void 0;
1051
+ if (sampleStrengthRaw && !Number.isFinite(sampleStrength)) {
1052
+ deps.fail(`Invalid --sample-strength: ${sampleStrengthRaw}`);
1053
+ }
1054
+ const result = await generateImageComposition(
1055
+ model,
1056
+ prompt,
1057
+ images,
1058
+ {
1059
+ ratio,
1060
+ resolution,
1061
+ sampleStrength,
1062
+ negativePrompt,
1063
+ intelligentRatio,
1064
+ wait,
1065
+ waitTimeoutSeconds: parsePositiveNumberOption2(args, "wait-timeout-seconds", deps),
1066
+ pollIntervalMs: parsePositiveNumberOption2(args, "poll-interval-ms", deps)
1067
+ },
1068
+ pick.token,
1069
+ buildRegionInfo(pick.region)
1070
+ );
1071
+ if (!Array.isArray(result)) {
1072
+ if (isJson) deps.printCommandJson("image.edit", result, { wait });
1073
+ else deps.printTaskInfo(result);
1074
+ return;
1075
+ }
1076
+ const urls = result;
1077
+ if (urls.length === 0) deps.fail("No image URL found in response.");
1078
+ const savedFiles = await downloadImages(urls, outputDir, "jimeng-image-edit", deps);
1079
+ if (isJson) {
1080
+ deps.printCommandJson(
1081
+ "image.edit",
1082
+ { data: urls.map((url) => ({ url })), files: savedFiles },
1083
+ { wait }
1084
+ );
1085
+ } else {
1086
+ deps.printDownloadSummary("image", savedFiles);
1087
+ }
1088
+ };
1089
+ const handleVideoGenerate = async (argv) => {
1090
+ const usage = deps.usageVideoGenerate();
1091
+ const args = minimist3(argv, {
1092
+ string: [
1093
+ "token",
1094
+ "region",
1095
+ "prompt",
1096
+ "mode",
1097
+ "image-file",
1098
+ "video-file",
1099
+ ...VIDEO_OMNI_IMAGE_SLOT_KEYS,
1100
+ ...VIDEO_OMNI_VIDEO_SLOT_KEYS,
1101
+ "model",
1102
+ "ratio",
1103
+ "resolution",
1104
+ "duration",
1105
+ "output-dir",
1106
+ "wait-timeout-seconds",
1107
+ "poll-interval-ms"
1108
+ ],
1109
+ boolean: ["help", "wait", "json"],
1110
+ default: { wait: true }
1111
+ });
1112
+ if (args.help) {
1113
+ console.log(usage);
1114
+ return;
1115
+ }
1116
+ const token = deps.getSingleString(args, "token");
1117
+ const region = deps.getRegionWithDefault(args);
1118
+ const prompt = ensurePrompt(deps.getSingleString(args, "prompt"), usage, deps);
1119
+ const cliMode = parseVideoCliMode(args, usage, {
1120
+ getSingleString: deps.getSingleString,
1121
+ toStringList: deps.toStringList,
1122
+ failWithUsage: deps.failWithUsage
1123
+ });
1124
+ const inputPlan = collectVideoInputPlan(args, usage, {
1125
+ getSingleString: deps.getSingleString,
1126
+ toStringList: deps.toStringList,
1127
+ failWithUsage: deps.failWithUsage
1128
+ });
1129
+ const outputDir = deps.getSingleString(args, "output-dir") || "./pic/cli-video-generate";
1130
+ const model = deps.getSingleString(args, "model") || (cliMode === "omni_reference" ? "jimeng-video-seedance-2.0-fast" : "jimeng-video-3.0");
1131
+ validateVideoModeAndModel(cliMode, model, inputPlan, usage, { failWithUsage: deps.failWithUsage });
1132
+ const functionMode = cliMode === "omni_reference" ? "omni_reference" : "first_last_frames";
1133
+ const ratio = deps.getSingleString(args, "ratio") || "1:1";
1134
+ const resolution = deps.getSingleString(args, "resolution") || "720p";
1135
+ const durationRaw = deps.getSingleString(args, "duration") || "5";
1136
+ const duration = Number(durationRaw);
1137
+ if (!Number.isFinite(duration) || duration <= 0 || !Number.isInteger(duration)) {
1138
+ deps.fail(`Invalid --duration: ${durationRaw}. Use a positive integer (seconds).`);
1139
+ }
1140
+ const wait = Boolean(args.wait);
1141
+ const isJson = Boolean(args.json);
1142
+ const requiredCapabilityTags = cliMode === "omni_reference" ? ["omni_reference"] : [];
1143
+ const pick = await deps.pickDirectTokenForGeneration(token, region, model, "video", requiredCapabilityTags);
1144
+ const directInputs = await buildDirectVideoInputPayload(cliMode, inputPlan, {
1145
+ isHttpUrl,
1146
+ pathExists: pathExists2,
1147
+ fail: deps.fail
1148
+ });
1149
+ const result = await generateVideo(
1150
+ model,
1151
+ prompt,
1152
+ {
1153
+ ratio,
1154
+ resolution,
1155
+ duration,
1156
+ filePaths: directInputs.filePaths,
1157
+ files: directInputs.files,
1158
+ httpRequest: directInputs.httpRequest,
1159
+ functionMode,
1160
+ wait,
1161
+ waitTimeoutSeconds: parsePositiveNumberOption2(args, "wait-timeout-seconds", deps),
1162
+ pollIntervalMs: parsePositiveNumberOption2(args, "poll-interval-ms", deps)
1163
+ },
1164
+ pick.token,
1165
+ buildRegionInfo(pick.region)
1166
+ );
1167
+ if (typeof result !== "string") {
1168
+ if (isJson) deps.printCommandJson("video.generate", result, { wait, mode: cliMode });
1169
+ else deps.printTaskInfo(result);
1170
+ return;
1171
+ }
1172
+ const videoUrl = result;
1173
+ const { buffer, contentType } = await downloadBinary(videoUrl, deps);
1174
+ const dir = path3.resolve(outputDir);
1175
+ await mkdir(dir, { recursive: true });
1176
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
1177
+ const ext = detectVideoExtension(contentType, videoUrl);
1178
+ const filePath = path3.join(dir, `jimeng-video-generate-${timestamp}.${ext}`);
1179
+ await writeFile(filePath, buffer);
1180
+ if (isJson) {
1181
+ deps.printCommandJson(
1182
+ "video.generate",
1183
+ { data: [{ url: videoUrl }], files: [filePath] },
1184
+ { wait, mode: cliMode }
1185
+ );
1186
+ } else {
1187
+ deps.printDownloadSummary("video", [filePath]);
1188
+ }
1189
+ };
1190
+ return {
1191
+ handleImageGenerate,
1192
+ handleImageEdit,
1193
+ handleVideoGenerate
1194
+ };
1195
+ }
1196
+
1197
+ // src/cli/app.ts
1198
+ var JSON_OPTION = " --json Output structured JSON";
1199
+ var HELP_OPTION = " --help Show help";
1200
+ function buildUsageText(usageLine, options, sections) {
1201
+ const lines = [
1202
+ "Usage:",
1203
+ usageLine,
1204
+ "",
1205
+ "Options:",
1206
+ ...options
1207
+ ];
1208
+ if (sections && sections.length > 0) {
1209
+ for (const section of sections) {
1210
+ lines.push("", section.title, ...section.lines);
1211
+ }
1212
+ }
1213
+ return lines.join("\n");
1214
+ }
1215
+ function usageRoot() {
1216
+ const commandLines = ROOT_COMMAND_ENTRIES.map(
1217
+ (entry) => ` ${entry.path.padEnd(32)}${entry.description}`
1218
+ );
1219
+ return [
1220
+ "Usage:",
1221
+ " jimeng <command> [subcommand] [options]",
1222
+ "",
1223
+ "Commands:",
1224
+ ...commandLines,
1225
+ "",
1226
+ ...ROOT_HELP_HINT_LINES
1227
+ ].join("\n");
1228
+ }
1229
+ function usageModelsList() {
1230
+ return buildUsageText(" jimeng models list [options]", [
1231
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1232
+ " --verbose Print rich model fields",
1233
+ " --json Print full JSON response",
1234
+ HELP_OPTION
1235
+ ]);
1236
+ }
1237
+ function usageTokenSubcommand(name) {
1238
+ const subcommand = TOKEN_SUBCOMMANDS_BY_NAME[name];
1239
+ return buildUsageText(subcommand.usageLine, subcommand.options, subcommand.sections);
1240
+ }
1241
+ function usageTokenRoot() {
1242
+ const subcommandLines = TOKEN_SUBCOMMANDS.map(
1243
+ (subcommand) => ` ${subcommand.name.padEnd(24)}${subcommand.description}`
1244
+ );
1245
+ return [
1246
+ "Usage:",
1247
+ " jimeng token <subcommand> [options]",
1248
+ "",
1249
+ "Subcommands:",
1250
+ ...subcommandLines,
1251
+ "",
1252
+ "Run `jimeng token <subcommand> --help` for details."
1253
+ ].join("\n");
1254
+ }
1255
+ function usageImageGenerate() {
1256
+ return buildUsageText(" jimeng image generate --prompt <text> [options]", [
1257
+ " --token <token> Optional, override token-pool selection",
1258
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1259
+ " --prompt <text> Required",
1260
+ " --model <model> Default jimeng-4.5",
1261
+ " --ratio <ratio> Default 1:1",
1262
+ " --resolution <res> Default 2k",
1263
+ " --negative-prompt <text> Optional",
1264
+ " --sample-strength <num> Optional, 0-1",
1265
+ " --intelligent-ratio Optional, enable intelligent ratio",
1266
+ " --wait / --no-wait Default wait; --no-wait returns task only",
1267
+ " --wait-timeout-seconds Optional wait timeout override",
1268
+ " --poll-interval-ms Optional poll interval override",
1269
+ JSON_OPTION,
1270
+ " --output-dir <dir> Default ./pic/cli-image-generate",
1271
+ HELP_OPTION
1272
+ ]);
1273
+ }
1274
+ function usageImageEdit() {
1275
+ return buildUsageText(
1276
+ " jimeng image edit --prompt <text> --image <path_or_url> [--image <path_or_url> ...] [options]",
1277
+ [
1278
+ " --token <token> Optional, override token-pool selection",
1279
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1280
+ " --prompt <text> Required",
1281
+ " --image <path_or_url> Required, can be repeated (1-10)",
1282
+ " --model <model> Default jimeng-4.5",
1283
+ " --ratio <ratio> Default 1:1",
1284
+ " --resolution <res> Default 2k",
1285
+ " --negative-prompt <text> Optional",
1286
+ " --sample-strength <num> Optional, 0-1",
1287
+ " --intelligent-ratio Optional, enable intelligent ratio",
1288
+ " --wait / --no-wait Default wait; --no-wait returns task only",
1289
+ " --wait-timeout-seconds Optional wait timeout override",
1290
+ " --poll-interval-ms Optional poll interval override",
1291
+ JSON_OPTION,
1292
+ " --output-dir <dir> Default ./pic/cli-image-edit",
1293
+ HELP_OPTION
1294
+ ],
1295
+ [
1296
+ {
1297
+ title: "Notes:",
1298
+ lines: [" - Image sources must be all local files or all URLs in one command."]
1299
+ }
1300
+ ]
1301
+ );
1302
+ }
1303
+ function usageVideoGenerate() {
1304
+ return buildUsageText(" jimeng video generate --prompt <text> [options]", [
1305
+ " --token <token> Optional, override token-pool selection",
1306
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1307
+ " --prompt <text> Required",
1308
+ " --mode <mode> Optional, text_to_video (default), image_to_video, first_last_frames, or omni_reference",
1309
+ " --image-file <input> Image input, can be repeated (path or URL)",
1310
+ " --video-file <input> Video input, can be repeated (path or URL, omni only)",
1311
+ " --image-file-1 <input> Explicit image slot (1-9) for omni_reference",
1312
+ " --image-file-2 ... -9 More explicit image slots for omni_reference",
1313
+ " --video-file-1 <input> Explicit video slot (1-3) for omni_reference",
1314
+ " --video-file-2 ... -3 More explicit video slots for omni_reference",
1315
+ " --model <model> Default jimeng-video-3.0 (jimeng-video-seedance-2.0-fast in omni_reference)",
1316
+ " --ratio <ratio> Default 1:1",
1317
+ " --resolution <res> Default 720p",
1318
+ " --duration <seconds> Default 5",
1319
+ " --wait / --no-wait Default wait; --no-wait returns task only",
1320
+ " --wait-timeout-seconds Optional wait timeout override",
1321
+ " --poll-interval-ms Optional poll interval override",
1322
+ JSON_OPTION,
1323
+ " --output-dir <dir> Default ./pic/cli-video-generate",
1324
+ HELP_OPTION
1325
+ ], [
1326
+ {
1327
+ title: "Examples:",
1328
+ lines: [
1329
+ ' jimeng video generate --mode text_to_video --prompt "A fox runs in snow"',
1330
+ ' jimeng video generate --mode image_to_video --prompt "Camera slowly pushes in" --image-file ./first.png',
1331
+ ' jimeng video generate --mode first_last_frames --prompt "Transition day to night" --image-file ./first.png --image-file ./last.png',
1332
+ ' jimeng video generate --mode omni_reference --model jimeng-video-seedance-2.0-fast --prompt "Use @image_file_1 for character and @video_file_1 for motion" --image-file ./character.png --video-file ./motion.mp4'
1333
+ ]
1334
+ },
1335
+ {
1336
+ title: "Notes:",
1337
+ lines: [
1338
+ " - text_to_video: no image/video input allowed.",
1339
+ " - image_to_video: exactly 1 --image-file input, no --video-file.",
1340
+ " - first_last_frames: 1-2 --image-file inputs, no --video-file.",
1341
+ " - omni_reference: 1-9 images and 0-3 videos (at least one material).",
1342
+ " - omni_reference supports model jimeng-video-seedance-2.0 or jimeng-video-seedance-2.0-fast.",
1343
+ " - Use @image_file_N / @video_file_N in prompt for omni_reference."
1344
+ ]
1345
+ }
1346
+ ]);
1347
+ }
1348
+ function usageTaskGet() {
1349
+ return buildUsageText(" jimeng task get --task-id <id> [options]", [
1350
+ " --token <token> Optional, override token-pool selection",
1351
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1352
+ " --task-id <id> Required history/task id",
1353
+ " --type <type> Optional image or video",
1354
+ " --response-format <fmt> Optional url or b64_json",
1355
+ JSON_OPTION,
1356
+ HELP_OPTION
1357
+ ]);
1358
+ }
1359
+ function usageTaskWait() {
1360
+ return buildUsageText(" jimeng task wait --task-id <id> [options]", [
1361
+ " --token <token> Optional, override token-pool selection",
1362
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1363
+ " --task-id <id> Required history/task id",
1364
+ " --type <type> Optional image or video",
1365
+ " --response-format <fmt> Optional url or b64_json",
1366
+ " --wait-timeout-seconds Optional wait timeout override",
1367
+ " --poll-interval-ms Optional poll interval override",
1368
+ JSON_OPTION,
1369
+ HELP_OPTION
1370
+ ]);
1371
+ }
1372
+ function configureCliLogging(command) {
1373
+ if (process2.env.JIMENG_CLI_VERBOSE_LOGS === "true") {
1374
+ process2.env.JIMENG_CLI_SILENT_LOGS = "false";
1375
+ return;
1376
+ }
1377
+ process2.env.JIMENG_CLI_SILENT_LOGS = "true";
1378
+ config_default.system.log_level = "fatal";
1379
+ config_default.system.debug = false;
1380
+ config_default.system.requestLog = false;
1381
+ logger_default.info = () => void 0;
1382
+ logger_default.debug = () => void 0;
1383
+ logger_default.warn = () => void 0;
1384
+ logger_default.error = () => void 0;
1385
+ console.info = () => void 0;
1386
+ console.debug = () => void 0;
1387
+ console.warn = () => void 0;
1388
+ }
1389
+ function fail(message) {
1390
+ throw new Error(message);
1391
+ }
1392
+ function failWithUsage(reason, usage) {
1393
+ fail(`${reason}
1394
+
1395
+ ${usage}`);
1396
+ }
1397
+ function getSingleString(args, key) {
1398
+ const raw = args[key];
1399
+ if (typeof raw === "string" && raw.trim().length > 0) return raw.trim();
1400
+ return void 0;
1401
+ }
1402
+ function getRegionWithDefault(args) {
1403
+ return getSingleString(args, "region") || "cn";
1404
+ }
1405
+ function toStringList(raw) {
1406
+ if (typeof raw === "string") return raw.split(",").map((item) => item.trim()).filter(Boolean);
1407
+ if (Array.isArray(raw)) {
1408
+ return raw.flatMap((item) => typeof item === "string" ? item.split(",") : []).map((item) => item.trim()).filter(Boolean);
1409
+ }
1410
+ return [];
1411
+ }
1412
+ var tokenPoolReady = false;
1413
+ async function ensureTokenPoolReady() {
1414
+ if (tokenPoolReady) return;
1415
+ await session_pool_default.init();
1416
+ tokenPoolReady = true;
1417
+ }
1418
+ function parseRegionOrFail(region) {
1419
+ if (!region) return void 0;
1420
+ const parsed = parseRegionCode(region);
1421
+ if (!parsed) fail("Invalid --region. Use cn/us/hk/jp/sg.");
1422
+ return parsed;
1423
+ }
1424
+ async function pickDirectTokenForGeneration(token, region, requestedModel, taskType, requiredCapabilityTags = []) {
1425
+ await ensureTokenPoolReady();
1426
+ const tokenPick = session_pool_default.pickTokenForRequest({
1427
+ authorization: token ? `Bearer ${token}` : void 0,
1428
+ requestedModel,
1429
+ taskType,
1430
+ requiredCapabilityTags,
1431
+ xRegion: region
1432
+ });
1433
+ if (!tokenPick.token || !tokenPick.region) {
1434
+ fail(tokenPick.reason || "No direct token available. Provide --token and --region, or configure token-pool.");
1435
+ }
1436
+ return { token: tokenPick.token, region: tokenPick.region };
1437
+ }
1438
+ async function pickDirectTokenForTask(token, region) {
1439
+ var _a;
1440
+ await ensureTokenPoolReady();
1441
+ const parsedRegion = parseRegionOrFail(region);
1442
+ if (token) {
1443
+ const fromPool = (_a = session_pool_default.getTokenEntry(token)) == null ? void 0 : _a.region;
1444
+ const finalRegion = parsedRegion || fromPool;
1445
+ if (!finalRegion) {
1446
+ fail("Missing region for direct task mode. Provide --region or register token region in token-pool.");
1447
+ }
1448
+ return { token, region: finalRegion };
1449
+ }
1450
+ const candidates = session_pool_default.getEntries(false).filter((item) => item.enabled && item.live !== false && item.region).filter((item) => parsedRegion ? item.region === parsedRegion : true);
1451
+ if (candidates.length === 0) {
1452
+ fail("No token available for direct task mode. Provide --token --region or configure token-pool.");
1453
+ }
1454
+ return { token: candidates[0].token, region: candidates[0].region };
1455
+ }
1456
+ function unwrapBody(payload) {
1457
+ if (!payload || typeof payload !== "object") return payload;
1458
+ const body = payload;
1459
+ if ("data" in body && ("code" in body || "message" in body)) {
1460
+ return body.data;
1461
+ }
1462
+ return payload;
1463
+ }
1464
+ function printJson(value) {
1465
+ console.log(JSON.stringify(value, null, 2));
1466
+ }
1467
+ function printCommandJson(command, data, meta) {
1468
+ const payload = {
1469
+ object: "jimeng_cli_result",
1470
+ command,
1471
+ data
1472
+ };
1473
+ if (meta && Object.keys(meta).length > 0) {
1474
+ payload.meta = meta;
1475
+ }
1476
+ printJson(payload);
1477
+ }
1478
+ function printDownloadSummary(kind, files) {
1479
+ const label = kind === "image" ? "images" : "video";
1480
+ console.log(`Downloaded ${files.length} ${label}.`);
1481
+ for (const file of files) {
1482
+ console.log(`- ${file}`);
1483
+ }
1484
+ }
1485
+ function isHelpKeyword(value) {
1486
+ return value === "--help" || value === "-h" || value === "help";
1487
+ }
1488
+ var TOKEN_SUBCOMMANDS = createTokenSubcommands({
1489
+ getUsage: (name) => usageTokenSubcommand(name),
1490
+ getSingleString,
1491
+ getRegionWithDefault,
1492
+ toStringList,
1493
+ parseRegionOrFail,
1494
+ ensureTokenPoolReady,
1495
+ fail,
1496
+ failWithUsage,
1497
+ printJson,
1498
+ printCommandJson,
1499
+ unwrapBody,
1500
+ jsonOption: JSON_OPTION,
1501
+ helpOption: HELP_OPTION
1502
+ });
1503
+ var queryHandlers = createQueryCommandHandlers({
1504
+ usageModelsList,
1505
+ usageTaskGet,
1506
+ usageTaskWait,
1507
+ getSingleString,
1508
+ getRegionWithDefault,
1509
+ parseRegionOrFail,
1510
+ ensureTokenPoolReady,
1511
+ pickDirectTokenForTask,
1512
+ fail,
1513
+ printJson,
1514
+ printCommandJson,
1515
+ unwrapBody
1516
+ });
1517
+ var mediaHandlers = createMediaCommandHandlers({
1518
+ usageImageGenerate,
1519
+ usageImageEdit,
1520
+ usageVideoGenerate,
1521
+ getSingleString,
1522
+ getRegionWithDefault,
1523
+ toStringList,
1524
+ fail,
1525
+ failWithUsage,
1526
+ pickDirectTokenForGeneration,
1527
+ printCommandJson,
1528
+ printDownloadSummary,
1529
+ printTaskInfo: (task) => queryHandlers.printTaskInfo(task)
1530
+ });
1531
+ var TOKEN_SUBCOMMANDS_BY_NAME = Object.fromEntries(
1532
+ TOKEN_SUBCOMMANDS.map((subcommand) => [subcommand.name, subcommand])
1533
+ );
1534
+ function buildHandlersMap(subcommands) {
1535
+ return Object.fromEntries(subcommands.map((item) => [item.name, item.handler]));
1536
+ }
1537
+ var COMMAND_SPECS = [
1538
+ {
1539
+ name: "models",
1540
+ description: "Model commands",
1541
+ subcommands: [{ name: "list", description: "List available models", handler: queryHandlers.handleModelsList }],
1542
+ usage: usageRoot
1543
+ },
1544
+ {
1545
+ name: "image",
1546
+ description: "Image commands",
1547
+ subcommands: [
1548
+ { name: "generate", description: "Generate image from text", handler: mediaHandlers.handleImageGenerate },
1549
+ { name: "edit", description: "Edit image(s) with prompt", handler: mediaHandlers.handleImageEdit }
1550
+ ],
1551
+ usage: usageRoot
1552
+ },
1553
+ {
1554
+ name: "video",
1555
+ description: "Video commands",
1556
+ subcommands: [
1557
+ {
1558
+ name: "generate",
1559
+ description: "Generate video from multimodal references",
1560
+ handler: mediaHandlers.handleVideoGenerate
1561
+ }
1562
+ ],
1563
+ usage: usageRoot
1564
+ },
1565
+ {
1566
+ name: "task",
1567
+ description: "Task commands",
1568
+ subcommands: [
1569
+ { name: "get", description: "Get task status", handler: queryHandlers.handleTaskGet },
1570
+ { name: "wait", description: "Wait until task completion", handler: queryHandlers.handleTaskWait }
1571
+ ],
1572
+ usage: usageRoot
1573
+ },
1574
+ {
1575
+ name: "token",
1576
+ description: "Token management commands",
1577
+ subcommands: TOKEN_SUBCOMMANDS.map((subcommand) => ({
1578
+ name: subcommand.name,
1579
+ description: subcommand.description,
1580
+ handler: subcommand.handler
1581
+ })),
1582
+ usage: usageTokenRoot,
1583
+ showAsGrouped: true
1584
+ }
1585
+ ];
1586
+ var COMMAND_SPECS_BY_NAME = Object.fromEntries(
1587
+ COMMAND_SPECS.map((spec) => [spec.name, spec])
1588
+ );
1589
+ var ROOT_COMMAND_ENTRIES = COMMAND_SPECS.flatMap((spec) => {
1590
+ if (spec.handler) {
1591
+ return [{ path: spec.name, description: spec.description }];
1592
+ }
1593
+ if (!spec.subcommands || spec.subcommands.length === 0) {
1594
+ return [{ path: spec.name, description: spec.description }];
1595
+ }
1596
+ if (spec.showAsGrouped) {
1597
+ return [{ path: `${spec.name} <subcommand>`, description: spec.description }];
1598
+ }
1599
+ return spec.subcommands.map((subcommand) => ({
1600
+ path: `${spec.name} ${subcommand.name}`,
1601
+ description: subcommand.description
1602
+ }));
1603
+ });
1604
+ var ROOT_HELP_HINT_LINES = [
1605
+ "Run `jimeng <command> --help` for command details.",
1606
+ ...COMMAND_SPECS.filter((spec) => spec.showAsGrouped).map((spec) => `Run \`jimeng ${spec.name} --help\` for ${spec.name} subcommands.`)
1607
+ ];
1608
+ async function dispatchSubcommand(subcommand, argv, handlers, usage, unknownLabel) {
1609
+ if (!subcommand || isHelpKeyword(subcommand)) {
1610
+ console.log(usage);
1611
+ return true;
1612
+ }
1613
+ const handler = handlers[subcommand];
1614
+ if (!handler) {
1615
+ failWithUsage(`Unknown ${unknownLabel}: ${subcommand}`, usage);
1616
+ }
1617
+ await handler(argv);
1618
+ return true;
1619
+ }
1620
+ async function run() {
1621
+ const [command, subcommand, ...rest] = process2.argv.slice(2);
1622
+ configureCliLogging(command);
1623
+ if (!command || isHelpKeyword(command)) {
1624
+ console.log(usageRoot());
1625
+ return;
1626
+ }
1627
+ const spec = COMMAND_SPECS_BY_NAME[command];
1628
+ if (!spec) {
1629
+ failWithUsage(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`, usageRoot());
1630
+ }
1631
+ if (spec.handler) {
1632
+ await spec.handler(rest);
1633
+ return;
1634
+ }
1635
+ if (spec.subcommands) {
1636
+ const handlers = buildHandlersMap(spec.subcommands);
1637
+ if (await dispatchSubcommand(
1638
+ subcommand,
1639
+ process2.argv.slice(3),
1640
+ handlers,
1641
+ spec.usage ? spec.usage() : usageRoot(),
1642
+ `${command} subcommand`
1643
+ )) {
1644
+ return;
1645
+ }
1646
+ }
1647
+ failWithUsage(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`, usageRoot());
1648
+ }
1649
+ function runCli() {
1650
+ run().catch((error) => {
1651
+ const message = error instanceof Error ? error.message : String(error);
1652
+ const isJson = process2.argv.includes("--json");
1653
+ if (isJson) {
1654
+ printCommandJson("error", { message });
1655
+ } else {
1656
+ console.error(`Error: ${message}`);
1657
+ }
1658
+ process2.exit(1);
1659
+ });
1660
+ }
1661
+
1662
+ // src/cli/index.ts
1663
+ runCli();
1664
+ //# sourceMappingURL=index.js.map