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.
- package/LICENSE +674 -0
- package/README.md +91 -0
- package/dist/chunk-JZY62VNI.js +4762 -0
- package/dist/chunk-JZY62VNI.js.map +1 -0
- package/dist/cli/index.cjs +6415 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1664 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.cjs +5584 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +2 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +831 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +87 -0
|
@@ -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
|