jimeng-cli 0.2.1 → 0.3.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/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  generateImageComposition,
6
6
  generateImages,
7
7
  generateVideo,
8
+ getAssetList,
8
9
  getCredit,
9
10
  getLiveModels,
10
11
  getTaskResponse,
@@ -12,9 +13,11 @@ import {
12
13
  logger_default,
13
14
  parseRegionCode,
14
15
  receiveCredit,
16
+ refreshAllTokenModels,
15
17
  session_pool_default,
18
+ upscaleImage,
16
19
  waitForTaskResponse
17
- } from "../chunk-3SUCLOAC.js";
20
+ } from "../chunk-2IIK4X7C.js";
18
21
 
19
22
  // src/cli/app.ts
20
23
  import process2 from "process";
@@ -495,6 +498,34 @@ function printTaskInfo(task, deps) {
495
498
  }
496
499
  }
497
500
  function createQueryCommandHandlers(deps) {
501
+ const handleModelsRefresh = async (argv) => {
502
+ const args = minimist2(argv, {
503
+ boolean: ["help", "json"]
504
+ });
505
+ if (args.help) {
506
+ console.log(deps.usageModelsRefresh());
507
+ return;
508
+ }
509
+ await deps.ensureTokenPoolReady();
510
+ const results = await refreshAllTokenModels();
511
+ const isJson = Boolean(args.json);
512
+ if (isJson) {
513
+ deps.printCommandJson("models.refresh", results);
514
+ return;
515
+ }
516
+ if (results.length === 0) {
517
+ console.log("No enabled+live tokens found in pool. Nothing to refresh.");
518
+ return;
519
+ }
520
+ console.log(`Refreshed ${results.length} token(s).`);
521
+ console.log("");
522
+ console.log("token region imageModels videoModels capabilityTags error");
523
+ for (const r of results) {
524
+ const tags = r.capabilityTags.length > 0 ? r.capabilityTags.join(",") : "-";
525
+ const err = r.error ? r.error.slice(0, 60) : "-";
526
+ console.log(`${r.token} ${r.region} ${r.imageModels} ${r.videoModels} ${tags} ${err}`);
527
+ }
528
+ };
498
529
  const handleModelsList = async (argv) => {
499
530
  const args = minimist2(argv, {
500
531
  string: ["region", "token"],
@@ -638,10 +669,62 @@ ${deps.usageTaskWait()}`);
638
669
  if (isJson) deps.printCommandJson("task.wait", taskInfo);
639
670
  else printTaskInfo(taskInfo, deps);
640
671
  };
672
+ const handleTaskList = async (argv) => {
673
+ const args = minimist2(argv, {
674
+ string: ["token", "region", "type", "count"],
675
+ boolean: ["help", "json"]
676
+ });
677
+ if (args.help) {
678
+ console.log(deps.usageTaskList());
679
+ return;
680
+ }
681
+ const token = deps.getSingleString(args, "token");
682
+ const region = deps.getRegionWithDefault(args);
683
+ const type = deps.getSingleString(args, "type");
684
+ const countRaw = deps.getSingleString(args, "count");
685
+ const count = countRaw ? Number(countRaw) : 20;
686
+ const isJson = Boolean(args.json);
687
+ if (type && type !== "image" && type !== "video" && type !== "all") {
688
+ deps.fail(`Invalid --type: ${type}. Use image, video, or all.`);
689
+ }
690
+ const pick = await deps.pickDirectTokenForTask(token, region);
691
+ const result = await getAssetList(
692
+ pick.token,
693
+ buildRegionInfo(pick.region),
694
+ {
695
+ count: Number.isFinite(count) && count > 0 ? count : 20,
696
+ type
697
+ }
698
+ );
699
+ if (isJson) {
700
+ deps.printCommandJson("task.list", {
701
+ has_more: result.hasMore,
702
+ next_offset: result.nextOffset,
703
+ total: result.items.length,
704
+ items: result.items
705
+ });
706
+ return;
707
+ }
708
+ console.log(`Total: ${result.items.length} items${result.hasMore ? " (more available)" : ""}
709
+ `);
710
+ for (const item of result.items) {
711
+ const typeLabel = item.type === 1 ? "IMG" : "VID";
712
+ const statusLabel = item.status === 144 || item.status === 10 ? "DONE" : item.status === 30 ? "FAIL" : "PROC";
713
+ const time = item.createdTime > 0 ? new Date(item.createdTime * 1e3).toLocaleString() : "-";
714
+ const modelShort = item.modelName || item.modelReqKey || "-";
715
+ const promptShort = item.prompt.length > 50 ? item.prompt.slice(0, 50) + "..." : item.prompt;
716
+ console.log(`${item.id} ${typeLabel} ${statusLabel.padEnd(4)} ${time} ${modelShort.padEnd(20)} ${promptShort}`);
717
+ if (item.imageUrl) {
718
+ console.log(` ${item.imageUrl}`);
719
+ }
720
+ }
721
+ };
641
722
  return {
642
723
  handleModelsList,
724
+ handleModelsRefresh,
643
725
  handleTaskGet,
644
726
  handleTaskWait,
727
+ handleTaskList,
645
728
  printTaskInfo: (task) => {
646
729
  const normalized = collectTaskInfo(task, deps);
647
730
  if (!normalized) {
@@ -1187,13 +1270,257 @@ function createMediaCommandHandlers(deps) {
1187
1270
  deps.printDownloadSummary("video", [filePath]);
1188
1271
  }
1189
1272
  };
1273
+ const handleImageUpscale = async (argv) => {
1274
+ const args = minimist3(argv, {
1275
+ string: [
1276
+ "token",
1277
+ "region",
1278
+ "image",
1279
+ "model",
1280
+ "resolution",
1281
+ "output-dir",
1282
+ "wait-timeout-seconds",
1283
+ "poll-interval-ms"
1284
+ ],
1285
+ boolean: ["help", "wait", "json"],
1286
+ default: { wait: true }
1287
+ });
1288
+ if (args.help) {
1289
+ console.log(deps.usageImageUpscale());
1290
+ return;
1291
+ }
1292
+ const token = deps.getSingleString(args, "token");
1293
+ const region = deps.getRegionWithDefault(args);
1294
+ const imageSource = deps.getSingleString(args, "image");
1295
+ if (!imageSource) deps.failWithUsage("Missing required --image.", deps.usageImageUpscale());
1296
+ const outputDir = deps.getSingleString(args, "output-dir") || "./pic/cli-image-upscale";
1297
+ const model = deps.getSingleString(args, "model") || "jimeng-5.0";
1298
+ const resolution = deps.getSingleString(args, "resolution") || "4k";
1299
+ const wait = Boolean(args.wait);
1300
+ const isJson = Boolean(args.json);
1301
+ const pick = await deps.pickDirectTokenForGeneration(token, region, model, "image");
1302
+ let image;
1303
+ if (isHttpUrl(imageSource)) {
1304
+ image = imageSource;
1305
+ } else {
1306
+ const imagePath = path3.resolve(imageSource);
1307
+ if (!await pathExists2(imagePath)) deps.fail(`Image file not found: ${imagePath}`);
1308
+ image = await readFile2(imagePath);
1309
+ }
1310
+ const result = await upscaleImage(
1311
+ model,
1312
+ image,
1313
+ {
1314
+ resolution,
1315
+ wait,
1316
+ waitTimeoutSeconds: parsePositiveNumberOption2(args, "wait-timeout-seconds", deps),
1317
+ pollIntervalMs: parsePositiveNumberOption2(args, "poll-interval-ms", deps)
1318
+ },
1319
+ pick.token,
1320
+ buildRegionInfo(pick.region)
1321
+ );
1322
+ if (!Array.isArray(result)) {
1323
+ if (isJson) deps.printCommandJson("image.upscale", result, { wait });
1324
+ else deps.printTaskInfo(result);
1325
+ return;
1326
+ }
1327
+ const urls = result;
1328
+ if (urls.length === 0) deps.fail("No image URL found in response.");
1329
+ const savedFiles = await downloadImages(urls, outputDir, "jimeng-image-upscale", deps);
1330
+ if (isJson) {
1331
+ deps.printCommandJson(
1332
+ "image.upscale",
1333
+ { data: urls.map((url) => ({ url })), files: savedFiles },
1334
+ { wait, resolution }
1335
+ );
1336
+ } else {
1337
+ deps.printDownloadSummary("image", savedFiles);
1338
+ }
1339
+ };
1190
1340
  return {
1191
1341
  handleImageGenerate,
1192
1342
  handleImageEdit,
1343
+ handleImageUpscale,
1193
1344
  handleVideoGenerate
1194
1345
  };
1195
1346
  }
1196
1347
 
1348
+ // src/cli/login.ts
1349
+ import { execSync, spawn } from "child_process";
1350
+ import { existsSync } from "fs";
1351
+ import path4 from "path";
1352
+ import os from "os";
1353
+ import minimist4 from "minimist";
1354
+ var LOGIN_SCRIPT = path4.join(__dirname, "..", "..", "scripts", "jimeng_login_helper.py");
1355
+ function createLoginCommandHandler(deps) {
1356
+ return async (argv) => {
1357
+ const args = minimist4(argv, {
1358
+ string: ["region", "sessionid", "debug-port"],
1359
+ boolean: ["help", "headless", "json"]
1360
+ });
1361
+ if (args.help) {
1362
+ console.log(usageLogin());
1363
+ return;
1364
+ }
1365
+ const region = deps.getRegionWithDefault(args);
1366
+ const parsedRegion = deps.parseRegionOrFail(region);
1367
+ const isJson = Boolean(args.json);
1368
+ const sessionid = deps.getSingleString(args, "sessionid");
1369
+ const debugPort = deps.getSingleString(args, "debug-port") || "9333";
1370
+ const headless = Boolean(args.headless);
1371
+ if (sessionid) {
1372
+ await addSessionToPool(sessionid, parsedRegion || "cn", deps, isJson);
1373
+ return;
1374
+ }
1375
+ const pythonLoginResult = await tryPythonLogin(debugPort, headless);
1376
+ if (pythonLoginResult) {
1377
+ await addSessionToPool(pythonLoginResult, parsedRegion || "cn", deps, isJson);
1378
+ return;
1379
+ }
1380
+ console.log("");
1381
+ console.log("Auto login not available. Please provide your sessionid manually.");
1382
+ console.log("You can get it from:");
1383
+ console.log(" 1. Login at https://jimeng.jianying.com/ai-tool/home/");
1384
+ console.log(" 2. Open DevTools > Application > Cookies > sessionid");
1385
+ console.log("");
1386
+ console.log("Then run: jimeng login --sessionid <your_sessionid> --region cn");
1387
+ console.log("");
1388
+ console.log("Or install Python 3 to use the automatic browser login.");
1389
+ };
1390
+ }
1391
+ async function tryPythonLogin(debugPort, headless) {
1392
+ const loginScript = findLoginScript();
1393
+ if (!loginScript) {
1394
+ return null;
1395
+ }
1396
+ console.log(`Launching browser login (debug port: ${debugPort})...`);
1397
+ console.log("Please complete login in the Chrome window.\n");
1398
+ try {
1399
+ const cmdArgs = [loginScript, "--debug-port", debugPort];
1400
+ if (headless) cmdArgs.push("--headless");
1401
+ const result = await runPythonScript(cmdArgs);
1402
+ const output = result.stdout.trim();
1403
+ const match = output.match(/sessionid:\s*(\S+)/);
1404
+ if (match) {
1405
+ return match[1];
1406
+ }
1407
+ console.log("Login script completed but sessionid not found in output.");
1408
+ return null;
1409
+ } catch (error) {
1410
+ console.log(`Auto login failed: ${error.message}`);
1411
+ return null;
1412
+ }
1413
+ }
1414
+ function findLoginScript() {
1415
+ if (existsSync(LOGIN_SCRIPT)) {
1416
+ return LOGIN_SCRIPT;
1417
+ }
1418
+ const candidates = [
1419
+ path4.join(process.cwd(), "jimeng_login.py"),
1420
+ path4.join(os.homedir(), ".jimeng_login.py")
1421
+ ];
1422
+ try {
1423
+ const whichResult = execSync("which jimeng_login.py 2>/dev/null || echo ''", { encoding: "utf-8" }).trim();
1424
+ if (whichResult) return whichResult;
1425
+ } catch {
1426
+ }
1427
+ for (const candidate of candidates) {
1428
+ if (existsSync(candidate)) return candidate;
1429
+ }
1430
+ return null;
1431
+ }
1432
+ function runPythonScript(args) {
1433
+ return new Promise((resolve, reject) => {
1434
+ var _a, _b;
1435
+ const pythonCmd = process.platform === "win32" ? "python" : "python3";
1436
+ const proc = spawn(pythonCmd, args, {
1437
+ stdio: ["ignore", "pipe", "pipe"],
1438
+ env: { ...process.env }
1439
+ });
1440
+ let stdout = "";
1441
+ let stderr = "";
1442
+ (_a = proc.stdout) == null ? void 0 : _a.on("data", (data) => {
1443
+ stdout += data.toString();
1444
+ const lines = data.toString().split("\n").filter(Boolean);
1445
+ for (const line of lines) {
1446
+ console.log(line);
1447
+ }
1448
+ });
1449
+ (_b = proc.stderr) == null ? void 0 : _b.on("data", (data) => {
1450
+ stderr += data.toString();
1451
+ });
1452
+ proc.on("close", (code) => {
1453
+ if (code === 0) {
1454
+ resolve({ stdout, stderr });
1455
+ } else {
1456
+ reject(new Error(`Login script exited with code ${code}: ${stderr.slice(-200)}`));
1457
+ }
1458
+ });
1459
+ proc.on("error", (error) => {
1460
+ reject(new Error(`Failed to run login script: ${error.message}`));
1461
+ });
1462
+ });
1463
+ }
1464
+ async function addSessionToPool(sessionid, region, deps, isJson) {
1465
+ await deps.ensureTokenPoolReady();
1466
+ const { added, total } = await session_pool_default.addTokens([sessionid], { defaultRegion: region });
1467
+ if (added > 0) {
1468
+ const masked = sessionid.length > 10 ? `${sessionid.slice(0, 4)}...${sessionid.slice(-4)}` : "***";
1469
+ let creditInfo = "";
1470
+ try {
1471
+ const { totalCredit } = await getCredit(sessionid, buildRegionInfo(region));
1472
+ creditInfo = ` (credits: ${totalCredit})`;
1473
+ } catch {
1474
+ creditInfo = " (credit check failed)";
1475
+ }
1476
+ if (isJson) {
1477
+ deps.printCommandJson("login", {
1478
+ token: masked,
1479
+ region,
1480
+ added: true,
1481
+ total,
1482
+ creditCheck: creditInfo.trim()
1483
+ });
1484
+ } else {
1485
+ console.log(`
1486
+ Login successful!`);
1487
+ console.log(` Token: ${masked}`);
1488
+ console.log(` Region: ${region}${creditInfo}`);
1489
+ console.log(` Pool size: ${total} token(s)`);
1490
+ }
1491
+ } else {
1492
+ if (isJson) {
1493
+ deps.printCommandJson("login", {
1494
+ added: false,
1495
+ total,
1496
+ reason: "token already exists in pool"
1497
+ });
1498
+ } else {
1499
+ console.log(`Token already exists in pool. Pool size: ${total} token(s)`);
1500
+ }
1501
+ }
1502
+ }
1503
+ function usageLogin() {
1504
+ return [
1505
+ "Usage:",
1506
+ " jimeng login [options]",
1507
+ "",
1508
+ "Options:",
1509
+ " --region <region> Region for the token (default cn)",
1510
+ " --sessionid <token> Add sessionid directly (skip browser login)",
1511
+ " --debug-port <port> Chrome debug port (default 9333)",
1512
+ " --headless Run Chrome in headless mode",
1513
+ " --json Output structured JSON",
1514
+ " --help Show help",
1515
+ "",
1516
+ "Notes:",
1517
+ " - Without --sessionid, launches Chrome for browser login (requires Python 3).",
1518
+ " - With --sessionid, directly adds the token to the pool.",
1519
+ " - After login, the token is automatically added to the token pool.",
1520
+ " - Token is validated by checking credit balance."
1521
+ ].join("\n");
1522
+ }
1523
+
1197
1524
  // src/cli/app.ts
1198
1525
  var JSON_OPTION = " --json Output structured JSON";
1199
1526
  var HELP_OPTION = " --help Show help";
@@ -1234,6 +1561,21 @@ function usageModelsList() {
1234
1561
  HELP_OPTION
1235
1562
  ]);
1236
1563
  }
1564
+ function usageModelsRefresh() {
1565
+ return buildUsageText(" jimeng models refresh [options]", [
1566
+ " --json Output structured JSON",
1567
+ HELP_OPTION
1568
+ ], [
1569
+ {
1570
+ title: "Notes:",
1571
+ lines: [
1572
+ " - Refreshes dynamicCapabilities (imageModels, videoModels, capabilityTags) for",
1573
+ " all enabled+live tokens in the token pool.",
1574
+ " - Results are persisted to token-pool.json automatically."
1575
+ ]
1576
+ }
1577
+ ]);
1578
+ }
1237
1579
  function usageTokenSubcommand(name) {
1238
1580
  const subcommand = TOKEN_SUBCOMMANDS_BY_NAME[name];
1239
1581
  return buildUsageText(subcommand.usageLine, subcommand.options, subcommand.sections);
@@ -1300,6 +1642,34 @@ function usageImageEdit() {
1300
1642
  ]
1301
1643
  );
1302
1644
  }
1645
+ function usageImageUpscale() {
1646
+ return buildUsageText(
1647
+ " jimeng image upscale --image <path_or_url> [options]",
1648
+ [
1649
+ " --token <token> Optional, override token-pool selection",
1650
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1651
+ " --image <path_or_url> Required, local file or URL",
1652
+ " --model <model> Default jimeng-5.0",
1653
+ " --resolution <res> Default 4k (target resolution)",
1654
+ " --wait / --no-wait Default wait; --no-wait returns task only",
1655
+ " --wait-timeout-seconds Optional wait timeout override",
1656
+ " --poll-interval-ms Optional poll interval override",
1657
+ JSON_OPTION,
1658
+ " --output-dir <dir> Default ./pic/cli-image-upscale",
1659
+ HELP_OPTION
1660
+ ],
1661
+ [
1662
+ {
1663
+ title: "Notes:",
1664
+ lines: [
1665
+ " - Upscales an existing image to higher resolution using super_resolution.",
1666
+ " - Supports 2k and 4k target resolutions.",
1667
+ " - Image source can be a local file path or HTTP URL."
1668
+ ]
1669
+ }
1670
+ ]
1671
+ );
1672
+ }
1303
1673
  function usageVideoGenerate() {
1304
1674
  return buildUsageText(" jimeng video generate --prompt <text> [options]", [
1305
1675
  " --token <token> Optional, override token-pool selection",
@@ -1369,6 +1739,16 @@ function usageTaskWait() {
1369
1739
  HELP_OPTION
1370
1740
  ]);
1371
1741
  }
1742
+ function usageTaskList() {
1743
+ return buildUsageText(" jimeng task list [options]", [
1744
+ " --token <token> Optional, override token-pool selection",
1745
+ " --region <region> X-Region header, default cn (cn/us/hk/jp/sg)",
1746
+ " --type <type> Filter by type: image, video, or all (default all)",
1747
+ " --count <num> Number of items per page (default 20)",
1748
+ JSON_OPTION,
1749
+ HELP_OPTION
1750
+ ]);
1751
+ }
1372
1752
  function configureCliLogging(command) {
1373
1753
  if (process2.env.JIMENG_CLI_VERBOSE_LOGS === "true") {
1374
1754
  process2.env.JIMENG_CLI_SILENT_LOGS = "false";
@@ -1502,8 +1882,10 @@ var TOKEN_SUBCOMMANDS = createTokenSubcommands({
1502
1882
  });
1503
1883
  var queryHandlers = createQueryCommandHandlers({
1504
1884
  usageModelsList,
1885
+ usageModelsRefresh,
1505
1886
  usageTaskGet,
1506
1887
  usageTaskWait,
1888
+ usageTaskList,
1507
1889
  getSingleString,
1508
1890
  getRegionWithDefault,
1509
1891
  parseRegionOrFail,
@@ -1517,6 +1899,7 @@ var queryHandlers = createQueryCommandHandlers({
1517
1899
  var mediaHandlers = createMediaCommandHandlers({
1518
1900
  usageImageGenerate,
1519
1901
  usageImageEdit,
1902
+ usageImageUpscale,
1520
1903
  usageVideoGenerate,
1521
1904
  getSingleString,
1522
1905
  getRegionWithDefault,
@@ -1535,10 +1918,26 @@ function buildHandlersMap(subcommands) {
1535
1918
  return Object.fromEntries(subcommands.map((item) => [item.name, item.handler]));
1536
1919
  }
1537
1920
  var COMMAND_SPECS = [
1921
+ {
1922
+ name: "login",
1923
+ description: "Login and add session to token pool",
1924
+ handler: createLoginCommandHandler({
1925
+ getSingleString,
1926
+ getRegionWithDefault,
1927
+ parseRegionOrFail,
1928
+ ensureTokenPoolReady,
1929
+ fail,
1930
+ printJson,
1931
+ printCommandJson
1932
+ })
1933
+ },
1538
1934
  {
1539
1935
  name: "models",
1540
1936
  description: "Model commands",
1541
- subcommands: [{ name: "list", description: "List available models", handler: queryHandlers.handleModelsList }],
1937
+ subcommands: [
1938
+ { name: "list", description: "List available models", handler: queryHandlers.handleModelsList },
1939
+ { name: "refresh", description: "Refresh token dynamic capabilities (model list)", handler: queryHandlers.handleModelsRefresh }
1940
+ ],
1542
1941
  usage: usageRoot
1543
1942
  },
1544
1943
  {
@@ -1546,7 +1945,8 @@ var COMMAND_SPECS = [
1546
1945
  description: "Image commands",
1547
1946
  subcommands: [
1548
1947
  { name: "generate", description: "Generate image from text", handler: mediaHandlers.handleImageGenerate },
1549
- { name: "edit", description: "Edit image(s) with prompt", handler: mediaHandlers.handleImageEdit }
1948
+ { name: "edit", description: "Edit image(s) with prompt", handler: mediaHandlers.handleImageEdit },
1949
+ { name: "upscale", description: "Upscale image to higher resolution", handler: mediaHandlers.handleImageUpscale }
1550
1950
  ],
1551
1951
  usage: usageRoot
1552
1952
  },
@@ -1567,7 +1967,8 @@ var COMMAND_SPECS = [
1567
1967
  description: "Task commands",
1568
1968
  subcommands: [
1569
1969
  { name: "get", description: "Get task status", handler: queryHandlers.handleTaskGet },
1570
- { name: "wait", description: "Wait until task completion", handler: queryHandlers.handleTaskWait }
1970
+ { name: "wait", description: "Wait until task completion", handler: queryHandlers.handleTaskWait },
1971
+ { name: "list", description: "List task history", handler: queryHandlers.handleTaskList }
1571
1972
  ],
1572
1973
  usage: usageRoot
1573
1974
  },
@@ -1629,7 +2030,7 @@ async function run() {
1629
2030
  failWithUsage(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`, usageRoot());
1630
2031
  }
1631
2032
  if (spec.handler) {
1632
- await spec.handler(rest);
2033
+ await spec.handler(subcommand ? [subcommand, ...rest] : rest);
1633
2034
  return;
1634
2035
  }
1635
2036
  if (spec.subcommands) {