@mrgoonie/multix 0.5.1 → 0.6.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.js CHANGED
@@ -414,9 +414,9 @@ var BytePlusClient = class {
414
414
  }
415
415
  apiKey;
416
416
  baseUrl;
417
- async request(path46, opts = {}) {
417
+ async request(path47, opts = {}) {
418
418
  const base = this.baseUrl.replace(/\/$/, "");
419
- const url = new URL(base + path46);
419
+ const url = new URL(base + path47);
420
420
  if (opts.query) {
421
421
  for (const [k, v] of Object.entries(opts.query)) {
422
422
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -434,14 +434,14 @@ var BytePlusClient = class {
434
434
  timeoutMs: opts.timeoutMs ?? 12e4
435
435
  });
436
436
  }
437
- get(path46, query, logger) {
438
- return this.request(path46, { method: "GET", query, logger });
437
+ get(path47, query, logger) {
438
+ return this.request(path47, { method: "GET", query, logger });
439
439
  }
440
- post(path46, body, logger) {
441
- return this.request(path46, { method: "POST", body, logger });
440
+ post(path47, body, logger) {
441
+ return this.request(path47, { method: "POST", body, logger });
442
442
  }
443
- delete(path46, logger) {
444
- return this.request(path46, { method: "DELETE", logger });
443
+ delete(path47, logger) {
444
+ return this.request(path47, { method: "DELETE", logger });
445
445
  }
446
446
  };
447
447
  function createBytePlusClient() {
@@ -569,10 +569,197 @@ Task: ${taskId}`);
569
569
  );
570
570
  }
571
571
 
572
+ // src/core/image-webp-finalize.ts
573
+ init_errors();
574
+ import { spawnSync } from "child_process";
575
+ import fs6 from "fs";
576
+ import path6 from "path";
577
+ import { Option } from "commander";
578
+
579
+ // src/core/image-header.ts
580
+ import fs5 from "fs";
581
+ function sniffImageKind(buf) {
582
+ if (buf.length >= 8 && buf.readUInt32BE(0) === 2303741511) return "png";
583
+ if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "jpeg";
584
+ if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP")
585
+ return "webp";
586
+ return void 0;
587
+ }
588
+ function readPng(buf) {
589
+ if (buf.length < 33) return void 0;
590
+ const width = buf.readUInt32BE(16);
591
+ const height = buf.readUInt32BE(20);
592
+ const colorType = buf[25];
593
+ let hasAlpha = colorType === 4 || colorType === 6;
594
+ let offset = 8;
595
+ while (!hasAlpha && offset + 8 <= buf.length) {
596
+ const len = buf.readUInt32BE(offset);
597
+ const type = buf.toString("ascii", offset + 4, offset + 8);
598
+ if (type === "tRNS") hasAlpha = true;
599
+ if (type === "IDAT" || type === "IEND") break;
600
+ offset += 12 + len;
601
+ }
602
+ return { kind: "png", width, height, hasAlpha };
603
+ }
604
+ function readJpeg(buf) {
605
+ let offset = 2;
606
+ while (offset + 9 < buf.length) {
607
+ if (buf[offset] !== 255) {
608
+ offset++;
609
+ continue;
610
+ }
611
+ const marker = buf[offset + 1] ?? 0;
612
+ if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
613
+ return {
614
+ kind: "jpeg",
615
+ height: buf.readUInt16BE(offset + 5),
616
+ width: buf.readUInt16BE(offset + 7),
617
+ hasAlpha: false
618
+ };
619
+ }
620
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
621
+ offset += 2;
622
+ continue;
623
+ }
624
+ offset += 2 + buf.readUInt16BE(offset + 2);
625
+ }
626
+ return void 0;
627
+ }
628
+ function readWebp(buf) {
629
+ if (buf.length < 30) return void 0;
630
+ const chunk = buf.toString("ascii", 12, 16);
631
+ if (chunk === "VP8X") {
632
+ const flags = buf[20] ?? 0;
633
+ return {
634
+ kind: "webp",
635
+ width: 1 + buf.readUIntLE(24, 3),
636
+ height: 1 + buf.readUIntLE(27, 3),
637
+ hasAlpha: (flags & 16) !== 0
638
+ };
639
+ }
640
+ if (chunk === "VP8L") {
641
+ const bits = buf.readUInt32LE(21);
642
+ return {
643
+ kind: "webp",
644
+ width: 1 + (bits & 16383),
645
+ height: 1 + (bits >>> 14 & 16383),
646
+ hasAlpha: (bits >>> 28 & 1) === 1
647
+ };
648
+ }
649
+ if (chunk === "VP8 ") {
650
+ return {
651
+ kind: "webp",
652
+ width: buf.readUInt16LE(26) & 16383,
653
+ height: buf.readUInt16LE(28) & 16383,
654
+ hasAlpha: false
655
+ };
656
+ }
657
+ return void 0;
658
+ }
659
+ function readImageHeader(buf) {
660
+ const kind = sniffImageKind(buf);
661
+ if (kind === "png") return readPng(buf);
662
+ if (kind === "jpeg") return readJpeg(buf);
663
+ if (kind === "webp") return readWebp(buf);
664
+ return void 0;
665
+ }
666
+ function readImageHeaderFromFile(file) {
667
+ return readImageHeader(fs5.readFileSync(file));
668
+ }
669
+
670
+ // src/core/image-webp-finalize.ts
671
+ var CWEBP_ARGS = ["-q", "85", "-m", "6", "-metadata", "none"];
672
+ var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tif", ".tiff"]);
673
+ function imageFormatOption() {
674
+ return new Option(
675
+ "--image-format <mode>",
676
+ "Final image format: webp (default, cwebp -q 85) | original (keep provider format/quality)"
677
+ );
678
+ }
679
+ function noWebpOption() {
680
+ return new Option("--no-webp", "Keep the provider's original image format and quality");
681
+ }
682
+ function resolveImageFormatMode(opts, explicitProviderFormat = false) {
683
+ if (opts.webp === false && opts.imageFormat === void 0) return "original";
684
+ const raw = opts.imageFormat ?? process.env.MULTIX_IMAGE_FORMAT;
685
+ if (raw !== void 0 && raw !== "") {
686
+ const mode = raw.toLowerCase();
687
+ if (mode !== "webp" && mode !== "original") {
688
+ throw new ValidationError(`Invalid --image-format: ${raw}. Valid: webp, original`);
689
+ }
690
+ return mode;
691
+ }
692
+ if (explicitProviderFormat) return "original";
693
+ const ext = opts.output ? path6.extname(opts.output).toLowerCase() : "";
694
+ if (ext && ext !== ".webp" && IMAGE_EXTS.has(ext)) return "original";
695
+ return "webp";
696
+ }
697
+ function hasCwebp() {
698
+ const res = spawnSync("cwebp", ["-version"], { stdio: "ignore" });
699
+ return !res.error && res.status === 0;
700
+ }
701
+ function webpTarget(file) {
702
+ const ext = path6.extname(file);
703
+ return ext.toLowerCase() === ".webp" ? file : `${file.slice(0, -ext.length || void 0)}.webp`;
704
+ }
705
+ function convertToWebp(file, logger, keepPath = false) {
706
+ const src = readImageHeaderFromFile(file);
707
+ if (!src) {
708
+ logger?.warn(`Skipping WebP conversion (unrecognized image): ${file}`);
709
+ return file;
710
+ }
711
+ if (src.kind === "webp") {
712
+ const target2 = keepPath ? file : webpTarget(file);
713
+ if (target2 !== file) fs6.renameSync(file, target2);
714
+ return target2;
715
+ }
716
+ const target = keepPath ? file : webpTarget(file);
717
+ const tmp = `${target}.tmp-${process.pid}.webp`;
718
+ const res = spawnSync("cwebp", [...CWEBP_ARGS, file, "-o", tmp], { encoding: "utf8" });
719
+ if (res.error || res.status !== 0) {
720
+ fs6.rmSync(tmp, { force: true });
721
+ logger?.warn(`cwebp failed, keeping original ${file}: ${res.stderr || res.error?.message}`);
722
+ return file;
723
+ }
724
+ const out = fs6.existsSync(tmp) ? readImageHeaderFromFile(tmp) : void 0;
725
+ const problem = !out ? "output is not a valid WebP" : out.width !== src.width || out.height !== src.height ? `size changed ${src.width}x${src.height} -> ${out.width}x${out.height}` : src.hasAlpha && !out.hasAlpha ? "transparency lost" : void 0;
726
+ if (problem) {
727
+ fs6.rmSync(tmp, { force: true });
728
+ logger?.warn(`WebP verification failed (${problem}), keeping original ${file}`);
729
+ return file;
730
+ }
731
+ if (target !== file) fs6.rmSync(file, { force: true });
732
+ fs6.renameSync(tmp, target);
733
+ logger?.debug(`WebP verified ${out?.width}x${out?.height} alpha=${out?.hasAlpha}: ${target}`);
734
+ return target;
735
+ }
736
+ function finalizeGeneratedImages(files, opts) {
737
+ const mode = resolveImageFormatMode(opts, opts.explicitProviderFormat);
738
+ if (mode === "original" || files.length === 0) return files;
739
+ if (!hasCwebp()) {
740
+ opts.logger?.warn(
741
+ "cwebp not found on PATH; keeping original image format (install libwebp, or pass --image-format original)"
742
+ );
743
+ return files;
744
+ }
745
+ const outputAbs = opts.output ? path6.resolve(opts.output) : void 0;
746
+ const wroteOutputDirectly = outputAbs !== void 0 && files.some((f) => path6.resolve(f) === outputAbs);
747
+ const finals = files.map(
748
+ (f) => convertToWebp(f, opts.logger, outputAbs !== void 0 && path6.resolve(f) === outputAbs)
749
+ );
750
+ if (opts.output && outputAbs && !wroteOutputDirectly && finals[0] && fs6.existsSync(outputAbs)) {
751
+ const outExt = path6.extname(outputAbs).toLowerCase();
752
+ if ((outExt === ".webp" || outExt === "") && path6.extname(finals[0]).toLowerCase() === ".webp") {
753
+ fs6.copyFileSync(finals[0], outputAbs);
754
+ }
755
+ }
756
+ return finals;
757
+ }
758
+
572
759
  // src/providers/byteplus/generators/image.ts
573
760
  init_http_client();
574
- import fs5 from "fs";
575
- import path6 from "path";
761
+ import fs7 from "fs";
762
+ import path7 from "path";
576
763
  var ASPECT_TO_SIZE = {
577
764
  "1:1": "2048x2048",
578
765
  "16:9": "2560x1440",
@@ -636,14 +823,14 @@ async function generateSeedream(client, opts) {
636
823
  }
637
824
  const ext = (() => {
638
825
  try {
639
- const e = path6.extname(new URL(url).pathname);
826
+ const e = path7.extname(new URL(url).pathname);
640
827
  return e || ".jpg";
641
828
  } catch {
642
829
  return ".jpg";
643
830
  }
644
831
  })();
645
- const filename = opts.output && items.length === 1 ? path6.resolve(opts.output) : path6.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
646
- fs5.mkdirSync(path6.dirname(filename), { recursive: true });
832
+ const filename = opts.output && items.length === 1 ? path7.resolve(opts.output) : path7.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
833
+ fs7.mkdirSync(path7.dirname(filename), { recursive: true });
647
834
  await downloadFile(url, filename);
648
835
  saved.push({ path: filename, url, seed: item.seed });
649
836
  logger?.success(`Saved ${filename}`);
@@ -658,7 +845,7 @@ function registerBytePlusGenerateCommand(parent) {
658
845
  "Reference image (URL or local path); repeatable",
659
846
  collect,
660
847
  []
661
- ).option("--output <path>", "Save single image to this path").option("-v, --verbose", "Verbose logging").action(
848
+ ).option("--output <path>", "Save single image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
662
849
  async (opts) => {
663
850
  const logger = createLogger({ verbose: opts.verbose ?? false });
664
851
  const client = createBytePlusClient();
@@ -674,9 +861,13 @@ function registerBytePlusGenerateCommand(parent) {
674
861
  output: opts.output,
675
862
  logger
676
863
  });
864
+ const finalImages = finalizeGeneratedImages(
865
+ saved.map((f) => f.path),
866
+ { ...opts, logger }
867
+ );
677
868
  console.log(`
678
- Generated ${saved.length} image(s):`);
679
- for (const f of saved) console.log(` ${f.path}`);
869
+ Generated ${finalImages.length} image(s):`);
870
+ for (const f of finalImages) console.log(` ${f}`);
680
871
  }
681
872
  );
682
873
  }
@@ -693,7 +884,7 @@ function registerBytePlusImageToImageCommand(parent) {
693
884
  "Reference image (URL or local path); repeatable for multi-ref",
694
885
  collect2,
695
886
  []
696
- ).option("-m, --model <id>", "Model id (default seedream-4-0-250828)").option("--size <size>", "Image size (e.g. 1024x1024, 1K, 2K, 4K)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9, 9:16); maps to size").option("-n, --num-images <n>", "Number of images", "1").option("--seed <n>", "Fixed seed").option("--no-watermark", "Disable watermark").option("--output <path>", "Save single image to this path").option("-v, --verbose", "Verbose logging").action(
887
+ ).option("-m, --model <id>", "Model id (default seedream-4-0-250828)").option("--size <size>", "Image size (e.g. 1024x1024, 1K, 2K, 4K)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9, 9:16); maps to size").option("-n, --num-images <n>", "Number of images", "1").option("--seed <n>", "Fixed seed").option("--no-watermark", "Disable watermark").option("--output <path>", "Save single image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
697
888
  async (opts) => {
698
889
  if (!opts.ref || opts.ref.length === 0) {
699
890
  throw new Error("At least one --ref is required for image-to-image.");
@@ -712,9 +903,13 @@ function registerBytePlusImageToImageCommand(parent) {
712
903
  output: opts.output,
713
904
  logger
714
905
  });
906
+ const finalImages = finalizeGeneratedImages(
907
+ saved.map((f) => f.path),
908
+ { ...opts, logger }
909
+ );
715
910
  console.log(`
716
- Generated ${saved.length} image(s):`);
717
- for (const f of saved) console.log(` ${f.path}`);
911
+ Generated ${finalImages.length} image(s):`);
912
+ for (const f of finalImages) console.log(` ${f}`);
718
913
  }
719
914
  );
720
915
  }
@@ -723,12 +918,12 @@ function collect2(value, prev) {
723
918
  }
724
919
 
725
920
  // src/providers/byteplus/commands/image-to-video.ts
726
- import path8 from "path";
921
+ import path9 from "path";
727
922
 
728
923
  // src/core/video-thumb.ts
729
924
  init_http_client();
730
- import fs6 from "fs";
731
- import path7 from "path";
925
+ import fs8 from "fs";
926
+ import path8 from "path";
732
927
  var THUMB_KEYS = /* @__PURE__ */ new Set([
733
928
  "cover_image_url",
734
929
  "coverImageUrl",
@@ -778,9 +973,9 @@ function detectThumbUrl(obj) {
778
973
  async function downloadThumbBeside(thumbUrl, videoPath, logger) {
779
974
  const match = thumbUrl.match(IMAGE_EXT_RE);
780
975
  const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
781
- const dir = path7.dirname(videoPath);
782
- const base = path7.basename(videoPath, path7.extname(videoPath));
783
- const dest = path7.join(dir, `${base}_thumb.${ext}`);
976
+ const dir = path8.dirname(videoPath);
977
+ const base = path8.basename(videoPath, path8.extname(videoPath));
978
+ const dest = path8.join(dir, `${base}_thumb.${ext}`);
784
979
  try {
785
980
  await downloadFile(thumbUrl, dest);
786
981
  logger?.success(`Saved thumb ${dest}`);
@@ -799,12 +994,12 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
799
994
  }
800
995
  const saved = await downloadThumbBeside(url, videoPath, opts.logger);
801
996
  if (saved && opts.copyTo) {
802
- const copyExt = path7.extname(saved);
803
- const copyBase = path7.basename(opts.copyTo, path7.extname(opts.copyTo));
804
- const copyDir = path7.dirname(path7.resolve(opts.copyTo));
805
- const copyDest = path7.join(copyDir, `${copyBase}_thumb${copyExt}`);
806
- fs6.mkdirSync(copyDir, { recursive: true });
807
- fs6.copyFileSync(saved, copyDest);
997
+ const copyExt = path8.extname(saved);
998
+ const copyBase = path8.basename(opts.copyTo, path8.extname(opts.copyTo));
999
+ const copyDir = path8.dirname(path8.resolve(opts.copyTo));
1000
+ const copyDest = path8.join(copyDir, `${copyBase}_thumb${copyExt}`);
1001
+ fs8.mkdirSync(copyDir, { recursive: true });
1002
+ fs8.copyFileSync(saved, copyDest);
808
1003
  opts.logger?.success(`Copied thumb to ${copyDest}`);
809
1004
  }
810
1005
  return saved;
@@ -932,7 +1127,7 @@ function registerBytePlusImageToVideoCommand(parent) {
932
1127
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
933
1128
  process.exit(1);
934
1129
  }
935
- const outPath = opts.output ?? path8.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1130
+ const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
936
1131
  await downloadVideo(url, outPath, logger);
937
1132
  await maybeDownloadThumb(final, outPath, {
938
1133
  skip: opts.thumb === false,
@@ -958,7 +1153,7 @@ Task: ${taskId}`);
958
1153
  }
959
1154
 
960
1155
  // src/providers/byteplus/commands/reference-to-video.ts
961
- import path9 from "path";
1156
+ import path10 from "path";
962
1157
 
963
1158
  // src/providers/byteplus/reference-input.ts
964
1159
  var MAX_TOTAL_REFS = 12;
@@ -990,10 +1185,10 @@ function toRef(kind, resolved, role) {
990
1185
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
991
1186
  }
992
1187
  async function resolveRefSpec(kind, spec, logger) {
993
- const { path: path46, role } = parseRefSpec(spec);
994
- if (kind === "image") return toRef(kind, await resolveImageInput(path46, logger), role);
995
- if (kind === "video") return toRef(kind, await resolveVideoInput(path46, logger), role);
996
- return toRef(kind, await resolveAudioInput(path46, logger), role);
1188
+ const { path: path47, role } = parseRefSpec(spec);
1189
+ if (kind === "image") return toRef(kind, await resolveImageInput(path47, logger), role);
1190
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path47, logger), role);
1191
+ return toRef(kind, await resolveAudioInput(path47, logger), role);
997
1192
  }
998
1193
  async function resolveRefSpecs(specs, logger) {
999
1194
  const out = [];
@@ -1090,7 +1285,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
1090
1285
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
1091
1286
  process.exit(1);
1092
1287
  }
1093
- const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1288
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1094
1289
  await downloadVideo(url, outPath, logger);
1095
1290
  await maybeDownloadThumb(final, outPath, {
1096
1291
  skip: opts.thumb === false,
@@ -1116,7 +1311,7 @@ Task: ${taskId}`);
1116
1311
  }
1117
1312
 
1118
1313
  // src/providers/byteplus/commands/status.ts
1119
- import path10 from "path";
1314
+ import path11 from "path";
1120
1315
  function registerBytePlusStatusCommand(parent) {
1121
1316
  parent.command("status <taskId>").description("Get a Seedance task's status; --wait + --download to fetch the result MP4").option("--wait", "Poll until terminal status").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download MP4 on success (implies --wait)").option("--output <path>", "Output MP4 path (with --download)").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
1122
1317
  async (taskId, opts) => {
@@ -1134,7 +1329,7 @@ function registerBytePlusStatusCommand(parent) {
1134
1329
  const videoUrl2 = content?.video_url;
1135
1330
  const fileUrl = content?.file_url;
1136
1331
  if (videoUrl2) {
1137
- const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1332
+ const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1138
1333
  await downloadVideo(videoUrl2, outPath, logger);
1139
1334
  await maybeDownloadThumb(status, outPath, {
1140
1335
  skip: opts.thumb === false,
@@ -1144,7 +1339,7 @@ function registerBytePlusStatusCommand(parent) {
1144
1339
  console.log(`Saved: ${outPath}`);
1145
1340
  } else if (fileUrl) {
1146
1341
  const ext = inferModelFileExt(fileUrl);
1147
- const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
1342
+ const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
1148
1343
  await downloadModelFile(fileUrl, outPath, logger);
1149
1344
  console.log(`Saved: ${outPath}`);
1150
1345
  } else {
@@ -1168,7 +1363,7 @@ function registerBytePlusStatusCommand(parent) {
1168
1363
  }
1169
1364
 
1170
1365
  // src/providers/byteplus/commands/video.ts
1171
- import path11 from "path";
1366
+ import path12 from "path";
1172
1367
  function registerBytePlusVideoCommand(parent) {
1173
1368
  parent.command("video").description("Generate a video from a text prompt via BytePlus Seedance 2.0").requiredOption("--prompt <text>", "Text prompt").option("-m, --model <id>", "Model id", BYTEPLUS_DEFAULTS.videoModel).option("--resolution <r>", "480p|720p|1080p|2k", BYTEPLUS_DEFAULTS.videoResolution).option("--duration <n>", "Duration seconds (4-15)", String(BYTEPLUS_DEFAULTS.videoDuration)).option(
1174
1369
  "--aspect-ratio <r>",
@@ -1210,7 +1405,7 @@ function registerBytePlusVideoCommand(parent) {
1210
1405
  logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
1211
1406
  process.exit(1);
1212
1407
  }
1213
- const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1408
+ const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1214
1409
  await downloadVideo(url, outPath, logger);
1215
1410
  await maybeDownloadThumb(final, outPath, {
1216
1411
  skip: opts.thumb === false,
@@ -1428,15 +1623,15 @@ async function downloadVideo2(url) {
1428
1623
 
1429
1624
  // src/providers/cloudflare/media-output.ts
1430
1625
  init_errors();
1431
- import fs7 from "fs";
1432
- import path12 from "path";
1626
+ import fs9 from "fs";
1627
+ import path13 from "path";
1433
1628
  function destination(filename, output) {
1434
- return output ? path12.resolve(output) : path12.join(getOutputDir(), filename);
1629
+ return output ? path13.resolve(output) : path13.join(getOutputDir(), filename);
1435
1630
  }
1436
1631
  function saveBytes(bytes, filename, output) {
1437
1632
  const dest = destination(filename, output);
1438
- fs7.mkdirSync(path12.dirname(dest), { recursive: true });
1439
- fs7.writeFileSync(dest, bytes);
1633
+ fs9.mkdirSync(path13.dirname(dest), { recursive: true });
1634
+ fs9.writeFileSync(dest, bytes);
1440
1635
  return dest;
1441
1636
  }
1442
1637
  function saveBase64Image(image, output) {
@@ -1553,7 +1748,7 @@ function registerCloudflareVideoCommand(parent) {
1553
1748
  // src/providers/cloudflare/commands/generate.ts
1554
1749
  init_errors();
1555
1750
  function registerCloudflareGenerateCommand(parent) {
1556
- parent.command("generate").description("Generate an image with Cloudflare Workers AI FLUX.1 Schnell").requiredOption("--prompt <text>", "Image prompt").option("--model <id>", "Cloudflare image model").option("--steps <n>", "Inference steps (1-8)", "4").option("--seed <n>", "Optional non-negative integer seed").option("--output <path>", "Save image at this path").option("-v, --verbose", "Verbose logging").action(
1751
+ parent.command("generate").description("Generate an image with Cloudflare Workers AI FLUX.1 Schnell").requiredOption("--prompt <text>", "Image prompt").option("--model <id>", "Cloudflare image model").option("--steps <n>", "Inference steps (1-8)", "4").option("--seed <n>", "Optional non-negative integer seed").option("--output <path>", "Save image at this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
1557
1752
  async (opts) => {
1558
1753
  if (!opts.prompt.trim()) throw new ValidationError("--prompt must not be empty");
1559
1754
  const logger = createLogger({ verbose: opts.verbose ?? false });
@@ -1575,8 +1770,9 @@ function registerCloudflareGenerateCommand(parent) {
1575
1770
  if (typeof result.image !== "string") {
1576
1771
  throw new ProviderError("Cloudflare returned no image result", "Cloudflare");
1577
1772
  }
1578
- const destination2 = saveBase64Image(result.image, opts.output);
1579
- logger.success(`Saved ${destination2}`);
1773
+ const saved = saveBase64Image(result.image, opts.output);
1774
+ logger.success(`Saved ${saved}`);
1775
+ const [destination2] = finalizeGeneratedImages([saved], { ...opts, logger });
1580
1776
  console.log(destination2);
1581
1777
  }
1582
1778
  );
@@ -1618,8 +1814,8 @@ function registerCloudflareCommands(program2) {
1618
1814
  }
1619
1815
 
1620
1816
  // src/providers/elevenlabs/client.ts
1621
- import fs8 from "fs";
1622
- import path13 from "path";
1817
+ import fs10 from "fs";
1818
+ import path14 from "path";
1623
1819
  init_errors();
1624
1820
  init_http_client();
1625
1821
 
@@ -1781,10 +1977,10 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
1781
1977
  return res.json();
1782
1978
  }
1783
1979
  function readFileAsBlob(filePath, fieldFilename) {
1784
- const abs = path13.resolve(filePath);
1785
- if (!fs8.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
1786
- const buf = fs8.readFileSync(abs);
1787
- const name = fieldFilename ?? path13.basename(abs);
1980
+ const abs = path14.resolve(filePath);
1981
+ if (!fs10.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
1982
+ const buf = fs10.readFileSync(abs);
1983
+ const name = fieldFilename ?? path14.basename(abs);
1788
1984
  return new File([new Uint8Array(buf)], name);
1789
1985
  }
1790
1986
  async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
@@ -1808,12 +2004,12 @@ async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
1808
2004
  function saveBytes2(opts) {
1809
2005
  const { bytes, task, ext, outputCopy, logger } = opts;
1810
2006
  const dir = getOutputDir();
1811
- const dest = path13.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
1812
- fs8.writeFileSync(dest, bytes);
2007
+ const dest = path14.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
2008
+ fs10.writeFileSync(dest, bytes);
1813
2009
  logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
1814
2010
  if (outputCopy) {
1815
- fs8.mkdirSync(path13.dirname(path13.resolve(outputCopy)), { recursive: true });
1816
- fs8.copyFileSync(dest, outputCopy);
2011
+ fs10.mkdirSync(path14.dirname(path14.resolve(outputCopy)), { recursive: true });
2012
+ fs10.copyFileSync(dest, outputCopy);
1817
2013
  }
1818
2014
  return dest;
1819
2015
  }
@@ -1852,14 +2048,14 @@ ${models.length} models`);
1852
2048
  }
1853
2049
 
1854
2050
  // src/providers/elevenlabs/commands/align.ts
1855
- import fs9 from "fs";
1856
- import path14 from "path";
2051
+ import fs11 from "fs";
2052
+ import path15 from "path";
1857
2053
  function registerAlignCommand(parent) {
1858
2054
  parent.command("align").description("Forced alignment of audio to a transcript (returns word/char timestamps)").requiredOption("--input <path>", "Source audio file").requiredOption("--text <str>", "Transcript text (or pass --text-file)").option("--text-file <path>", "Read transcript from file").option("--output <path>", "Save JSON to this path (default: multix-output)").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1859
2055
  const logger = createLogger({ verbose: opts.verbose ?? false });
1860
2056
  const apiKey = requireElevenLabsKey();
1861
2057
  let text = opts.text;
1862
- if (opts.textFile) text = fs9.readFileSync(path14.resolve(opts.textFile), "utf8");
2058
+ if (opts.textFile) text = fs11.readFileSync(path15.resolve(opts.textFile), "utf8");
1863
2059
  const form = new FormData();
1864
2060
  form.append("file", readFileAsBlob(opts.input));
1865
2061
  form.append("text", text);
@@ -1867,9 +2063,9 @@ function registerAlignCommand(parent) {
1867
2063
  const data = await apiPostMultipart("forced-alignment", form, apiKey, {
1868
2064
  timeoutMs: 6e5
1869
2065
  });
1870
- const dest = opts.output ? path14.resolve(opts.output) : path14.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
1871
- fs9.mkdirSync(path14.dirname(dest), { recursive: true });
1872
- fs9.writeFileSync(dest, JSON.stringify(data, null, 2));
2066
+ const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
2067
+ fs11.mkdirSync(path15.dirname(dest), { recursive: true });
2068
+ fs11.writeFileSync(dest, JSON.stringify(data, null, 2));
1873
2069
  logger.success(`Saved alignment: ${dest}`);
1874
2070
  if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
1875
2071
  });
@@ -1912,8 +2108,8 @@ function registerCloneCommand(parent) {
1912
2108
  // src/providers/elevenlabs/commands/dub.ts
1913
2109
  init_errors();
1914
2110
  init_http_client();
1915
- import fs10 from "fs";
1916
- import path15 from "path";
2111
+ import fs12 from "fs";
2112
+ import path16 from "path";
1917
2113
  function registerDubCommand(parent) {
1918
2114
  parent.command("dub").description("Dub a video/audio file into target languages (async)").option("--input <path>", "Source media file (use --input or --source-url)").option("--source-url <url>", "Public URL of source media").requiredOption("--target-lang <code>", "Target language ISO code (e.g. es, fr, de)").option("--source-lang <code>", "Source language (auto if omitted)").option("--num-speakers <n>", "Speaker count (auto if omitted)").option("--watermark", "Add ElevenLabs watermark", false).option("--start-time <s>", "Start time in seconds").option("--end-time <s>", "End time in seconds").option("--highest-resolution", "Use highest resolution video", false).option("--name <n>", "Project display name").option("--async", "Return job id immediately, do not wait", false).option("--wait", "Wait until status=dubbed (implied by --download)", false).option("--download", "Download dubbed media when ready", false).option("--poll-interval <s>", "Poll interval seconds", "10").option("--wait-timeout <ms>", "Poll timeout", "1800000").option("--output <path>", "Output path for downloaded dub").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1919
2115
  const logger = createLogger({ verbose: opts.verbose ?? false });
@@ -1956,7 +2152,7 @@ function registerDubCommand(parent) {
1956
2152
  );
1957
2153
  logger.success(`Dub complete: ${start.dubbing_id}`);
1958
2154
  if (opts.download) {
1959
- const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
2155
+ const dest = opts.output ? path16.resolve(opts.output) : path16.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
1960
2156
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
1961
2157
  await downloadDub(url, apiKey, dest);
1962
2158
  logger.success(`Saved: ${dest}`);
@@ -1971,7 +2167,7 @@ Dubbed file: ${dest}`);
1971
2167
  const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
1972
2168
  console.log(JSON.stringify(status, null, 2));
1973
2169
  if (opts.download) {
1974
- const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
2170
+ const dest = opts.output ? path16.resolve(opts.output) : path16.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
1975
2171
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
1976
2172
  await downloadDub(url, apiKey, dest);
1977
2173
  console.log(`Saved: ${dest}`);
@@ -1979,7 +2175,7 @@ Dubbed file: ${dest}`);
1979
2175
  });
1980
2176
  }
1981
2177
  async function downloadDub(url, apiKey, dest) {
1982
- fs10.mkdirSync(path15.dirname(dest), { recursive: true });
2178
+ fs12.mkdirSync(path16.dirname(dest), { recursive: true });
1983
2179
  const res = await globalThis.fetch(url, {
1984
2180
  headers: { "xi-api-key": apiKey },
1985
2181
  signal: AbortSignal.timeout(6e5)
@@ -1988,7 +2184,7 @@ async function downloadDub(url, apiKey, dest) {
1988
2184
  throw new Error(`Failed to download dub: HTTP ${res.status}`);
1989
2185
  }
1990
2186
  const arr = new Uint8Array(await res.arrayBuffer());
1991
- fs10.writeFileSync(dest, arr);
2187
+ fs12.writeFileSync(dest, arr);
1992
2188
  void downloadFile;
1993
2189
  }
1994
2190
 
@@ -2017,8 +2213,8 @@ Isolated audio: ${dest}`);
2017
2213
 
2018
2214
  // src/providers/elevenlabs/commands/music.ts
2019
2215
  init_errors();
2020
- import fs11 from "fs";
2021
- import path16 from "path";
2216
+ import fs13 from "fs";
2217
+ import path17 from "path";
2022
2218
  function registerMusicCommand(parent) {
2023
2219
  parent.command("music").description("Generate music from a text prompt or composition plan").option("--prompt <str>", "Music prompt (mutually exclusive with --plan)").option("--plan <path>", "Path to composition plan JSON file").option("--music-length-ms <n>", "Total length in milliseconds (default 30000)").option("--model <id>", "Music model", "music_v1").option(
2024
2220
  "--format <fmt>",
@@ -2033,9 +2229,9 @@ function registerMusicCommand(parent) {
2033
2229
  const payload = { model_id: opts.model };
2034
2230
  if (opts.prompt) payload.prompt = opts.prompt;
2035
2231
  if (opts.plan) {
2036
- const planPath = path16.resolve(opts.plan);
2037
- if (!fs11.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
2038
- const planJson = JSON.parse(fs11.readFileSync(planPath, "utf8"));
2232
+ const planPath = path17.resolve(opts.plan);
2233
+ if (!fs13.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
2234
+ const planJson = JSON.parse(fs13.readFileSync(planPath, "utf8"));
2039
2235
  payload.composition_plan = planJson;
2040
2236
  }
2041
2237
  if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
@@ -2094,8 +2290,8 @@ Generated SFX: ${dest}`);
2094
2290
  }
2095
2291
 
2096
2292
  // src/providers/elevenlabs/commands/transcribe.ts
2097
- import fs12 from "fs";
2098
- import path17 from "path";
2293
+ import fs14 from "fs";
2294
+ import path18 from "path";
2099
2295
  function registerTranscribeCommand(parent) {
2100
2296
  parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
2101
2297
  "--model <id>",
@@ -2118,7 +2314,7 @@ function registerTranscribeCommand(parent) {
2118
2314
  });
2119
2315
  const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
2120
2316
  const dir = getOutputDir();
2121
- const dest = opts.output ? path17.resolve(opts.output) : path17.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
2317
+ const dest = opts.output ? path18.resolve(opts.output) : path18.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
2122
2318
  let body;
2123
2319
  switch (opts.format) {
2124
2320
  case "json":
@@ -2133,8 +2329,8 @@ function registerTranscribeCommand(parent) {
2133
2329
  default:
2134
2330
  body = data.text ?? "";
2135
2331
  }
2136
- fs12.mkdirSync(path17.dirname(dest), { recursive: true });
2137
- fs12.writeFileSync(dest, body);
2332
+ fs14.mkdirSync(path18.dirname(dest), { recursive: true });
2333
+ fs14.writeFileSync(dest, body);
2138
2334
  logger.success(`Saved transcript: ${dest}`);
2139
2335
  if (data.language_code) {
2140
2336
  logger.info(
@@ -2381,16 +2577,16 @@ function registerElevenLabsCommands(program2) {
2381
2577
 
2382
2578
  // src/providers/gemini/commands/analyze.ts
2383
2579
  init_errors();
2384
- import fs15 from "fs";
2580
+ import fs17 from "fs";
2385
2581
 
2386
2582
  // src/providers/gemini/client.ts
2387
- import fs13 from "fs";
2388
- import path19 from "path";
2583
+ import fs15 from "fs";
2584
+ import path20 from "path";
2389
2585
  init_errors();
2390
2586
  init_http_client();
2391
2587
 
2392
2588
  // src/providers/gemini/task-resolver.ts
2393
- import path18 from "path";
2589
+ import path19 from "path";
2394
2590
  var MIME_MAP = {
2395
2591
  // Audio
2396
2592
  ".mp3": "audio/mp3",
@@ -2431,7 +2627,7 @@ var MIME_MAP = {
2431
2627
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
2432
2628
  };
2433
2629
  function getMimeType(filePath) {
2434
- const ext = path18.extname(filePath).toLowerCase();
2630
+ const ext = path19.extname(filePath).toLowerCase();
2435
2631
  return MIME_MAP[ext] ?? "application/octet-stream";
2436
2632
  }
2437
2633
  function requiresProcessingWait(mimeType) {
@@ -2457,8 +2653,8 @@ async function uploadFile(filePath, opts = {}) {
2457
2653
  const apiKey = requireApiKey();
2458
2654
  const { timeoutMs = 3e5, logger } = opts;
2459
2655
  const mimeType = getMimeType(filePath);
2460
- const fileBytes = fs13.readFileSync(filePath);
2461
- const displayName = path19.basename(filePath);
2656
+ const fileBytes = fs15.readFileSync(filePath);
2657
+ const displayName = path20.basename(filePath);
2462
2658
  const boundary = `multix_${Date.now()}`;
2463
2659
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
2464
2660
  const body = Buffer.concat([
@@ -2547,12 +2743,12 @@ function extractImages(resp) {
2547
2743
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
2548
2744
  }
2549
2745
  function inlinePart(filePath) {
2550
- const data = fs13.readFileSync(filePath).toString("base64");
2746
+ const data = fs15.readFileSync(filePath).toString("base64");
2551
2747
  const mimeType = getMimeType(filePath);
2552
2748
  return { inlineData: { mimeType, data } };
2553
2749
  }
2554
2750
  function shouldUseFileApi(filePath) {
2555
- const stat = fs13.statSync(filePath);
2751
+ const stat = fs15.statSync(filePath);
2556
2752
  return stat.size > 20 * 1024 * 1024;
2557
2753
  }
2558
2754
  function sleep(ms) {
@@ -2606,13 +2802,13 @@ function getDefaultModel(task) {
2606
2802
  }
2607
2803
 
2608
2804
  // src/providers/gemini/result-formatter.ts
2609
- import fs14 from "fs";
2610
- import path20 from "path";
2805
+ import fs16 from "fs";
2806
+ import path21 from "path";
2611
2807
  function formatResults(results, format, outputPath) {
2612
2808
  const content = renderFormat(results, format);
2613
2809
  if (outputPath) {
2614
- fs14.mkdirSync(path20.dirname(path20.resolve(outputPath)), { recursive: true });
2615
- fs14.writeFileSync(outputPath, content, "utf8");
2810
+ fs16.mkdirSync(path21.dirname(path21.resolve(outputPath)), { recursive: true });
2811
+ fs16.writeFileSync(outputPath, content, "utf8");
2616
2812
  } else {
2617
2813
  process.stdout.write(content);
2618
2814
  }
@@ -2679,7 +2875,7 @@ function registerAnalyzeCommand(parent) {
2679
2875
  const logger = createLogger({ verbose: opts.verbose ?? false });
2680
2876
  const model = opts.model ?? getDefaultModel("analyze");
2681
2877
  for (const f of opts.files) {
2682
- if (!fs15.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2878
+ if (!fs17.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2683
2879
  }
2684
2880
  const results = [];
2685
2881
  for (let i = 0; i < opts.files.length; i++) {
@@ -2725,14 +2921,14 @@ function registerAnalyzeCommand(parent) {
2725
2921
 
2726
2922
  // src/providers/gemini/commands/extract.ts
2727
2923
  init_errors();
2728
- import fs16 from "fs";
2924
+ import fs18 from "fs";
2729
2925
  function registerExtractCommand(parent) {
2730
2926
  parent.command("extract").description("Extract structured information from files with Gemini").requiredOption("--files <paths...>", "Input file paths").requiredOption("--prompt <text>", "Extraction prompt (required)").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
2731
2927
  async (opts) => {
2732
2928
  const logger = createLogger({ verbose: opts.verbose ?? false });
2733
2929
  const model = opts.model ?? getDefaultModel("extract");
2734
2930
  for (const f of opts.files) {
2735
- if (!fs16.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2931
+ if (!fs18.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2736
2932
  }
2737
2933
  const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
2738
2934
  const results = [];
@@ -2774,8 +2970,8 @@ function registerExtractCommand(parent) {
2774
2970
  init_errors();
2775
2971
 
2776
2972
  // src/providers/gemini/generators/speech.ts
2777
- import fs17 from "fs";
2778
- import path21 from "path";
2973
+ import fs19 from "fs";
2974
+ import path22 from "path";
2779
2975
 
2780
2976
  // src/providers/gemini/voices.ts
2781
2977
  var GEMINI_TTS_VOICES = [
@@ -2858,12 +3054,12 @@ async function generateGeminiSpeech(opts) {
2858
3054
  const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
2859
3055
  const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
2860
3056
  const outDir = getOutputDir();
2861
- const dest = path21.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
2862
- fs17.writeFileSync(dest, outBytes);
3057
+ const dest = path22.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
3058
+ fs19.writeFileSync(dest, outBytes);
2863
3059
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
2864
3060
  if (output) {
2865
- fs17.mkdirSync(path21.dirname(path21.resolve(output)), { recursive: true });
2866
- fs17.copyFileSync(dest, output);
3061
+ fs19.mkdirSync(path22.dirname(path22.resolve(output)), { recursive: true });
3062
+ fs19.copyFileSync(dest, output);
2867
3063
  logger?.success(`Copied to: ${output}`);
2868
3064
  }
2869
3065
  return { status: "success", generatedAudio: dest, model, mimeType: audio.mimeType };
@@ -2962,8 +3158,8 @@ function collectSpeakers(value, prev) {
2962
3158
  }
2963
3159
 
2964
3160
  // src/providers/gemini/commands/generate-video.ts
2965
- import fs18 from "fs";
2966
- import path22 from "path";
3161
+ import fs20 from "fs";
3162
+ import path23 from "path";
2967
3163
  init_errors();
2968
3164
  init_http_client();
2969
3165
  var BASE2 = "https://generativelanguage.googleapis.com";
@@ -2997,10 +3193,10 @@ function registerGenerateVideoCommand(parent) {
2997
3193
  const contentParts = [{ text: opts.prompt }];
2998
3194
  if (opts.referenceImages?.length) {
2999
3195
  for (const imgPath of opts.referenceImages.slice(0, 2)) {
3000
- if (!fs18.existsSync(imgPath))
3196
+ if (!fs20.existsSync(imgPath))
3001
3197
  throw new ValidationError(`Reference image not found: ${imgPath}`);
3002
- const data = fs18.readFileSync(imgPath).toString("base64");
3003
- const ext = path22.extname(imgPath).toLowerCase();
3198
+ const data = fs20.readFileSync(imgPath).toString("base64");
3199
+ const ext = path23.extname(imgPath).toLowerCase();
3004
3200
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
3005
3201
  contentParts.push({ inlineData: { mimeType, data } });
3006
3202
  }
@@ -3049,13 +3245,13 @@ function registerGenerateVideoCommand(parent) {
3049
3245
  );
3050
3246
  }
3051
3247
  const outDir = getOutputDir();
3052
- const dest = path22.join(outDir, `veo_generated_${Date.now()}.mp4`);
3248
+ const dest = path23.join(outDir, `veo_generated_${Date.now()}.mp4`);
3053
3249
  const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
3054
3250
  await downloadFile2(videoUri, dest);
3055
3251
  logger.success(`Saved video: ${dest}`);
3056
3252
  if (opts.output) {
3057
- fs18.mkdirSync(path22.dirname(path22.resolve(opts.output)), { recursive: true });
3058
- fs18.copyFileSync(dest, opts.output);
3253
+ fs20.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
3254
+ fs20.copyFileSync(dest, opts.output);
3059
3255
  logger.success(`Copied to: ${opts.output}`);
3060
3256
  }
3061
3257
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -3074,10 +3270,10 @@ function sleep2(ms) {
3074
3270
 
3075
3271
  // src/providers/gemini/commands/generate.ts
3076
3272
  init_errors();
3077
- import fs19 from "fs";
3078
- import path23 from "path";
3273
+ import fs21 from "fs";
3274
+ import path24 from "path";
3079
3275
  function registerGenerateCommand(parent) {
3080
- parent.command("generate").description("Generate images with Gemini (Nano Banana / Imagen 4)").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "Gemini image model to use").option("--aspect-ratio <ratio>", `Aspect ratio (${ASPECT_RATIOS.join("|")})`, "1:1").option("--num-images <n>", "Number of images to generate (1-4)", "1").option("--size <sz>", `Image size (${IMAGE_SIZES.join("|")})`).option("--output <path>", "Copy first generated image to this path").option("-v, --verbose", "Verbose logging").action(
3276
+ parent.command("generate").description("Generate images with Gemini (Nano Banana / Imagen 4)").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "Gemini image model to use").option("--aspect-ratio <ratio>", `Aspect ratio (${ASPECT_RATIOS.join("|")})`, "1:1").option("--num-images <n>", "Number of images to generate (1-4)", "1").option("--size <sz>", `Image size (${IMAGE_SIZES.join("|")})`).option("--output <path>", "Copy first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
3081
3277
  async (opts) => {
3082
3278
  const logger = createLogger({ verbose: opts.verbose ?? false });
3083
3279
  const model = opts.model ?? getDefaultModel("generate");
@@ -3117,8 +3313,8 @@ function registerGenerateCommand(parent) {
3117
3313
  const outDir = getOutputDir();
3118
3314
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
3119
3315
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
3120
- const dest = path23.join(outDir, fname);
3121
- fs19.writeFileSync(dest, Buffer.from(img.data, "base64"));
3316
+ const dest = path24.join(outDir, fname);
3317
+ fs21.writeFileSync(dest, Buffer.from(img.data, "base64"));
3122
3318
  savedFiles.push(dest);
3123
3319
  logger.success(`Saved: ${dest}`);
3124
3320
  }
@@ -3133,20 +3329,21 @@ function registerGenerateCommand(parent) {
3133
3329
  process.exit(1);
3134
3330
  }
3135
3331
  if (opts.output && savedFiles[0]) {
3136
- fs19.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
3137
- fs19.copyFileSync(savedFiles[0], opts.output);
3332
+ fs21.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
3333
+ fs21.copyFileSync(savedFiles[0], opts.output);
3138
3334
  logger.success(`Copied to: ${opts.output}`);
3139
3335
  }
3336
+ const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
3140
3337
  console.log(`
3141
- Generated ${savedFiles.length} image(s):`);
3142
- for (const f of savedFiles) console.log(` ${f}`);
3338
+ Generated ${finalImages.length} image(s):`);
3339
+ for (const f of finalImages) console.log(` ${f}`);
3143
3340
  }
3144
3341
  );
3145
3342
  }
3146
3343
 
3147
3344
  // src/providers/gemini/commands/image-to-image.ts
3148
- import fs20 from "fs";
3149
- import path24 from "path";
3345
+ import fs22 from "fs";
3346
+ import path25 from "path";
3150
3347
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
3151
3348
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
3152
3349
  function registerGeminiImageToImageCommand(parent) {
@@ -3157,7 +3354,7 @@ function registerGeminiImageToImageCommand(parent) {
3157
3354
  "Reference image (URL or local path); repeatable for multi-ref",
3158
3355
  collect4,
3159
3356
  []
3160
- ).option("-m, --model <id>", `Gemini image model (default ${IMAGE_MODEL_FALLBACK})`).option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
3357
+ ).option("-m, --model <id>", `Gemini image model (default ${IMAGE_MODEL_FALLBACK})`).option("--output <path>", "Save first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
3161
3358
  async (opts) => {
3162
3359
  if (!opts.ref || opts.ref.length === 0) {
3163
3360
  throw new Error("At least one --ref is required for image-to-image.");
@@ -3207,19 +3404,20 @@ function registerGeminiImageToImageCommand(parent) {
3207
3404
  const img = images[i];
3208
3405
  if (!img) continue;
3209
3406
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
3210
- const dest = path24.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
3211
- fs20.writeFileSync(dest, Buffer.from(img.data, "base64"));
3407
+ const dest = path25.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
3408
+ fs22.writeFileSync(dest, Buffer.from(img.data, "base64"));
3212
3409
  saved.push(dest);
3213
3410
  logger.success(`Saved: ${dest}`);
3214
3411
  }
3215
3412
  if (opts.output && saved[0]) {
3216
- fs20.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
3217
- fs20.copyFileSync(saved[0], opts.output);
3413
+ fs22.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
3414
+ fs22.copyFileSync(saved[0], opts.output);
3218
3415
  logger.success(`Copied to: ${opts.output}`);
3219
3416
  }
3417
+ const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
3220
3418
  console.log(`
3221
- Generated ${saved.length} image(s):`);
3222
- for (const f of saved) console.log(` ${f}`);
3419
+ Generated ${finalImages.length} image(s):`);
3420
+ for (const f of finalImages) console.log(` ${f}`);
3223
3421
  }
3224
3422
  );
3225
3423
  }
@@ -3241,7 +3439,7 @@ function sniffImageMime(buf, url) {
3241
3439
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
3242
3440
  return "image/webp";
3243
3441
  }
3244
- const ext = path24.extname(new URL(url).pathname).toLowerCase();
3442
+ const ext = path25.extname(new URL(url).pathname).toLowerCase();
3245
3443
  if (ext === ".png") return "image/png";
3246
3444
  if (ext === ".webp") return "image/webp";
3247
3445
  if (ext === ".gif") return "image/gif";
@@ -3249,17 +3447,17 @@ function sniffImageMime(buf, url) {
3249
3447
  }
3250
3448
 
3251
3449
  // src/providers/gemini/commands/image-to-video.ts
3252
- import fs21 from "fs";
3253
- import path25 from "path";
3450
+ import fs23 from "fs";
3451
+ import path26 from "path";
3254
3452
  init_errors();
3255
3453
  init_http_client();
3256
3454
  var BASE3 = "https://generativelanguage.googleapis.com";
3257
3455
  function imagePart(imgPath) {
3258
- if (!fs21.existsSync(imgPath)) {
3456
+ if (!fs23.existsSync(imgPath)) {
3259
3457
  throw new ValidationError(`Image not found: ${imgPath}`);
3260
3458
  }
3261
- const data = fs21.readFileSync(imgPath).toString("base64");
3262
- const ext = path25.extname(imgPath).toLowerCase();
3459
+ const data = fs23.readFileSync(imgPath).toString("base64");
3460
+ const ext = path26.extname(imgPath).toLowerCase();
3263
3461
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
3264
3462
  return { inlineData: { mimeType, data } };
3265
3463
  }
@@ -3332,12 +3530,12 @@ function registerGeminiImageToVideoCommand(parent) {
3332
3530
  "gemini"
3333
3531
  );
3334
3532
  }
3335
- const dest = path25.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
3533
+ const dest = path26.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
3336
3534
  await downloadFile(videoUri, dest);
3337
3535
  logger.success(`Saved video: ${dest}`);
3338
3536
  if (opts.output) {
3339
- fs21.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
3340
- fs21.copyFileSync(dest, opts.output);
3537
+ fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
3538
+ fs23.copyFileSync(dest, opts.output);
3341
3539
  logger.success(`Copied to: ${opts.output}`);
3342
3540
  }
3343
3541
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -3353,14 +3551,14 @@ Generated video: ${dest}`);
3353
3551
 
3354
3552
  // src/providers/gemini/commands/transcribe.ts
3355
3553
  init_errors();
3356
- import fs22 from "fs";
3554
+ import fs24 from "fs";
3357
3555
  function registerTranscribeCommand2(parent) {
3358
3556
  parent.command("transcribe").description("Transcribe audio or video files with Gemini").requiredOption("--files <paths...>", "Audio/video file paths to transcribe").option("--prompt <text>", "Transcription prompt", "Generate a transcript with timestamps").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
3359
3557
  async (opts) => {
3360
3558
  const logger = createLogger({ verbose: opts.verbose ?? false });
3361
3559
  const model = opts.model ?? getDefaultModel("transcribe");
3362
3560
  for (const f of opts.files) {
3363
- if (!fs22.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3561
+ if (!fs24.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3364
3562
  }
3365
3563
  const results = [];
3366
3564
  for (let i = 0; i < opts.files.length; i++) {
@@ -3418,8 +3616,8 @@ function registerGeminiCommands(program2) {
3418
3616
 
3419
3617
  // src/providers/leonardo/commands/generate.ts
3420
3618
  init_http_client();
3421
- import fs23 from "fs";
3422
- import path26 from "path";
3619
+ import fs25 from "fs";
3620
+ import path27 from "path";
3423
3621
 
3424
3622
  // src/providers/leonardo/client.ts
3425
3623
  init_errors();
@@ -3493,11 +3691,11 @@ var LeonardoClient = class {
3493
3691
  }
3494
3692
  apiKey;
3495
3693
  baseUrl;
3496
- async request(path46, opts = {}) {
3694
+ async request(path47, opts = {}) {
3497
3695
  const base = this.baseUrl.replace(/\/$/, "");
3498
- const v2Override = path46.startsWith("v2:");
3696
+ const v2Override = path47.startsWith("v2:");
3499
3697
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
3500
- const finalPath = v2Override ? path46.slice(3) : path46;
3698
+ const finalPath = v2Override ? path47.slice(3) : path47;
3501
3699
  const url = new URL(finalBase + finalPath);
3502
3700
  if (opts.query) {
3503
3701
  for (const [k, v] of Object.entries(opts.query)) {
@@ -3516,14 +3714,14 @@ var LeonardoClient = class {
3516
3714
  timeoutMs: opts.timeoutMs ?? 12e4
3517
3715
  });
3518
3716
  }
3519
- get(path46, query, logger) {
3520
- return this.request(path46, { method: "GET", query, logger });
3717
+ get(path47, query, logger) {
3718
+ return this.request(path47, { method: "GET", query, logger });
3521
3719
  }
3522
- post(path46, body, logger) {
3523
- return this.request(path46, { method: "POST", body, logger });
3720
+ post(path47, body, logger) {
3721
+ return this.request(path47, { method: "POST", body, logger });
3524
3722
  }
3525
- delete(path46, logger) {
3526
- return this.request(path46, { method: "DELETE", logger });
3723
+ delete(path47, logger) {
3724
+ return this.request(path47, { method: "DELETE", logger });
3527
3725
  }
3528
3726
  };
3529
3727
  function createLeonardoClient() {
@@ -3534,7 +3732,7 @@ function createLeonardoClient() {
3534
3732
  function extFromUrl(url, fallback = ".png") {
3535
3733
  try {
3536
3734
  const u = new URL(url);
3537
- const e = path26.extname(u.pathname);
3735
+ const e = path27.extname(u.pathname);
3538
3736
  return e.length > 0 ? e : fallback;
3539
3737
  } catch {
3540
3738
  return fallback;
@@ -3545,7 +3743,7 @@ function autoFilename(generationId, index, ext) {
3545
3743
  return `leonardo-${generationId.slice(0, 8)}-${String(index).padStart(2, "0")}${e}`;
3546
3744
  }
3547
3745
  function registerLeonardoGenerateCommand(parent) {
3548
- parent.command("generate <prompt>").alias("gen").description("Generate images from a text prompt via Leonardo").option("-m, --model <id>", "Model UUID or GPT-image id").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--alchemy", "Enable Alchemy").option("--ultra", "Enable Ultra mode").option("--contrast <n>", "Contrast").option("--style <uuid>", "Style UUID").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--enhance", "Enhance prompt").option("--quality <level>", "GPT Image quality: LOW|MEDIUM|HIGH", "HIGH").option("--output <path>", "Copy first image to this path (single-image only)").option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
3746
+ parent.command("generate <prompt>").alias("gen").description("Generate images from a text prompt via Leonardo").option("-m, --model <id>", "Model UUID or GPT-image id").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--alchemy", "Enable Alchemy").option("--ultra", "Enable Ultra mode").option("--contrast <n>", "Contrast").option("--style <uuid>", "Style UUID").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--enhance", "Enhance prompt").option("--quality <level>", "GPT Image quality: LOW|MEDIUM|HIGH", "HIGH").option("--output <path>", "Copy first image to this path (single-image only)").addOption(imageFormatOption()).addOption(noWebpOption()).option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
3549
3747
  async (prompt, opts) => {
3550
3748
  const logger = createLogger({ verbose: opts.verbose ?? false });
3551
3749
  const client = createLeonardoClient();
@@ -3619,7 +3817,7 @@ function registerLeonardoGenerateCommand(parent) {
3619
3817
  if (!img) continue;
3620
3818
  const ext = extFromUrl(img.url);
3621
3819
  const filename = autoFilename(generationId, i + 1, ext);
3622
- const filepath = path26.join(outDir, filename);
3820
+ const filepath = path27.join(outDir, filename);
3623
3821
  try {
3624
3822
  await downloadFile(img.url, filepath);
3625
3823
  saved.push(filepath);
@@ -3629,31 +3827,32 @@ function registerLeonardoGenerateCommand(parent) {
3629
3827
  }
3630
3828
  }
3631
3829
  if (opts.output && saved[0]) {
3632
- const dest = path26.resolve(opts.output);
3633
- fs23.mkdirSync(path26.dirname(dest), { recursive: true });
3634
- fs23.copyFileSync(saved[0], dest);
3830
+ const dest = path27.resolve(opts.output);
3831
+ fs25.mkdirSync(path27.dirname(dest), { recursive: true });
3832
+ fs25.copyFileSync(saved[0], dest);
3635
3833
  logger.success(`Copied to ${dest}`);
3636
3834
  }
3835
+ const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
3637
3836
  console.log(`
3638
- Generated ${saved.length} image(s):`);
3639
- for (const f of saved) console.log(` ${f}`);
3837
+ Generated ${finalImages.length} image(s):`);
3838
+ for (const f of finalImages) console.log(` ${f}`);
3640
3839
  }
3641
3840
  );
3642
3841
  }
3643
3842
 
3644
3843
  // src/providers/leonardo/commands/image-to-image.ts
3645
3844
  init_http_client();
3646
- import fs24 from "fs";
3647
- import path27 from "path";
3845
+ import fs26 from "fs";
3846
+ import path28 from "path";
3648
3847
  function extFromUrl2(url, fallback = ".png") {
3649
3848
  try {
3650
- return path27.extname(new URL(url).pathname) || fallback;
3849
+ return path28.extname(new URL(url).pathname) || fallback;
3651
3850
  } catch {
3652
3851
  return fallback;
3653
3852
  }
3654
3853
  }
3655
3854
  function registerLeonardoImageToImageCommand(parent) {
3656
- parent.command("image-to-image").alias("i2i").description("Generate an image conditioned on an existing Leonardo image id (init image)").requiredOption("--prompt <text>", "Text prompt describing the desired output").requiredOption("--ref <imageId>", "Existing Leonardo image id to use as init image").option("--image-type <type>", "GENERATED | UPLOADED", "GENERATED").option("--init-strength <n>", "Init image strength (0.0-0.9)", "0.5").option("-m, --model <id>", "Model UUID").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--output <path>", "Copy first image to this path (single-image only)").option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
3855
+ parent.command("image-to-image").alias("i2i").description("Generate an image conditioned on an existing Leonardo image id (init image)").requiredOption("--prompt <text>", "Text prompt describing the desired output").requiredOption("--ref <imageId>", "Existing Leonardo image id to use as init image").option("--image-type <type>", "GENERATED | UPLOADED", "GENERATED").option("--init-strength <n>", "Init image strength (0.0-0.9)", "0.5").option("-m, --model <id>", "Model UUID").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--output <path>", "Copy first image to this path (single-image only)").addOption(imageFormatOption()).addOption(noWebpOption()).option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
3657
3856
  async (opts) => {
3658
3857
  const logger = createLogger({ verbose: opts.verbose ?? false });
3659
3858
  const imageType = opts.imageType.toUpperCase();
@@ -3709,7 +3908,7 @@ function registerLeonardoImageToImageCommand(parent) {
3709
3908
  if (!img) continue;
3710
3909
  const ext = extFromUrl2(img.url);
3711
3910
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
3712
- const filepath = path27.join(outDir, filename);
3911
+ const filepath = path28.join(outDir, filename);
3713
3912
  try {
3714
3913
  await downloadFile(img.url, filepath);
3715
3914
  saved.push(filepath);
@@ -3719,21 +3918,22 @@ function registerLeonardoImageToImageCommand(parent) {
3719
3918
  }
3720
3919
  }
3721
3920
  if (opts.output && saved[0]) {
3722
- const dest = path27.resolve(opts.output);
3723
- fs24.mkdirSync(path27.dirname(dest), { recursive: true });
3724
- fs24.copyFileSync(saved[0], dest);
3921
+ const dest = path28.resolve(opts.output);
3922
+ fs26.mkdirSync(path28.dirname(dest), { recursive: true });
3923
+ fs26.copyFileSync(saved[0], dest);
3725
3924
  logger.success(`Copied to ${dest}`);
3726
3925
  }
3926
+ const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
3727
3927
  console.log(`
3728
- Generated ${saved.length} image(s):`);
3729
- for (const f of saved) console.log(` ${f}`);
3928
+ Generated ${finalImages.length} image(s):`);
3929
+ for (const f of finalImages) console.log(` ${f}`);
3730
3930
  }
3731
3931
  );
3732
3932
  }
3733
3933
 
3734
3934
  // src/providers/leonardo/commands/video.ts
3735
3935
  init_http_client();
3736
- import path28 from "path";
3936
+ import path29 from "path";
3737
3937
  function v2DimsFor(resolution) {
3738
3938
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
3739
3939
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -3844,7 +4044,7 @@ async function pollAndDownload(client, jobId, opts) {
3844
4044
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
3845
4045
  process.exit(1);
3846
4046
  }
3847
- const outPath = opts.output ?? path28.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
4047
+ const outPath = opts.output ?? path29.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
3848
4048
  await downloadFile(videoUrl2, outPath);
3849
4049
  logger.success(`Saved ${outPath}`);
3850
4050
  await maybeDownloadThumb(gen, outPath, {
@@ -3951,7 +4151,7 @@ function registerLeonardoModelsCommand(parent) {
3951
4151
 
3952
4152
  // src/providers/leonardo/commands/status.ts
3953
4153
  init_http_client();
3954
- import path29 from "path";
4154
+ import path30 from "path";
3955
4155
  var VIDEO_KEYS = ["motionMP4URL", "url"];
3956
4156
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
3957
4157
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -4016,7 +4216,7 @@ function registerLeonardoStatusCommand(parent) {
4016
4216
  const outDir = getOutputDir();
4017
4217
  let idx = 0;
4018
4218
  for (const url of videos) {
4019
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path29.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
4219
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path30.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
4020
4220
  await downloadFile(url, dest);
4021
4221
  logger.success(`Saved ${dest}`);
4022
4222
  await maybeDownloadThumb(gen, dest, {
@@ -4029,7 +4229,7 @@ function registerLeonardoStatusCommand(parent) {
4029
4229
  for (const url of images) {
4030
4230
  const m = url.match(IMAGE_EXT_RE2);
4031
4231
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
4032
- const dest = path29.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
4232
+ const dest = path30.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
4033
4233
  await downloadFile(url, dest);
4034
4234
  logger.success(`Saved ${dest}`);
4035
4235
  idx++;
@@ -4188,8 +4388,8 @@ function sleep4(ms) {
4188
4388
 
4189
4389
  // src/providers/minimax/generators/music.ts
4190
4390
  init_http_client();
4191
- import fs25 from "fs";
4192
- import path30 from "path";
4391
+ import fs27 from "fs";
4392
+ import path31 from "path";
4193
4393
  async function generateMinimaxMusic(opts) {
4194
4394
  const {
4195
4395
  apiKey,
@@ -4234,14 +4434,14 @@ async function generateMinimaxMusic(opts) {
4234
4434
  }
4235
4435
  const durationMs = resp.extra_info?.music_duration ?? 0;
4236
4436
  const outDir = getOutputDir();
4237
- const dest = path30.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
4437
+ const dest = path31.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
4238
4438
  try {
4239
4439
  if (audioData.startsWith("http")) {
4240
4440
  const bytes = await fetchBytes(audioData);
4241
- fs25.writeFileSync(dest, bytes);
4441
+ fs27.writeFileSync(dest, bytes);
4242
4442
  } else {
4243
4443
  const audioBytes = Buffer.from(audioData, "hex");
4244
- fs25.writeFileSync(dest, audioBytes);
4444
+ fs27.writeFileSync(dest, audioBytes);
4245
4445
  }
4246
4446
  } catch (e) {
4247
4447
  return {
@@ -4252,8 +4452,8 @@ async function generateMinimaxMusic(opts) {
4252
4452
  const durSec = durationMs / 1e3;
4253
4453
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
4254
4454
  if (output) {
4255
- fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4256
- fs25.copyFileSync(dest, output);
4455
+ fs27.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4456
+ fs27.copyFileSync(dest, output);
4257
4457
  }
4258
4458
  return { status: "success", generatedAudio: dest, durationMs, model };
4259
4459
  }
@@ -4327,8 +4527,8 @@ Generated music: ${result.generatedAudio}`);
4327
4527
  init_errors();
4328
4528
 
4329
4529
  // src/providers/minimax/generators/speech.ts
4330
- import fs26 from "fs";
4331
- import path31 from "path";
4530
+ import fs28 from "fs";
4531
+ import path32 from "path";
4332
4532
  async function generateMinimaxSpeech(opts) {
4333
4533
  const {
4334
4534
  apiKey,
@@ -4375,12 +4575,12 @@ async function generateMinimaxSpeech(opts) {
4375
4575
  const audioBytes = Buffer.from(audioHex, "hex");
4376
4576
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
4377
4577
  const outDir = getOutputDir();
4378
- const dest = path31.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4379
- fs26.writeFileSync(dest, audioBytes);
4578
+ const dest = path32.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4579
+ fs28.writeFileSync(dest, audioBytes);
4380
4580
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
4381
4581
  if (output) {
4382
- fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4383
- fs26.copyFileSync(dest, output);
4582
+ fs28.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4583
+ fs28.copyFileSync(dest, output);
4384
4584
  }
4385
4585
  return { status: "success", generatedAudio: dest, model };
4386
4586
  }
@@ -4427,8 +4627,8 @@ Generated audio: ${result.generatedAudio}`);
4427
4627
  init_errors();
4428
4628
 
4429
4629
  // src/providers/minimax/generators/video.ts
4430
- import fs27 from "fs";
4431
- import path32 from "path";
4630
+ import fs29 from "fs";
4631
+ import path33 from "path";
4432
4632
  async function generateMinimaxVideo(opts) {
4433
4633
  const {
4434
4634
  apiKey,
@@ -4474,18 +4674,18 @@ async function generateMinimaxVideo(opts) {
4474
4674
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
4475
4675
  }
4476
4676
  const outDir = getOutputDir();
4477
- const dest = path32.join(outDir, `minimax_video_${Date.now()}.mp4`);
4677
+ const dest = path33.join(outDir, `minimax_video_${Date.now()}.mp4`);
4478
4678
  try {
4479
4679
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
4480
4680
  } catch (e) {
4481
4681
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
4482
4682
  }
4483
4683
  const elapsed = (Date.now() - start) / 1e3;
4484
- const stat = fs27.statSync(dest);
4684
+ const stat = fs29.statSync(dest);
4485
4685
  const fileSizeMb = stat.size / (1024 * 1024);
4486
4686
  if (output) {
4487
- fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4488
- fs27.copyFileSync(dest, output);
4687
+ fs29.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
4688
+ fs29.copyFileSync(dest, output);
4489
4689
  }
4490
4690
  await maybeDownloadThumb(pollResult, dest, {
4491
4691
  skip: thumb === false,
@@ -4548,8 +4748,8 @@ Generated video: ${result.generatedVideo}`);
4548
4748
  // src/providers/minimax/generators/image.ts
4549
4749
  init_errors();
4550
4750
  init_http_client();
4551
- import fs28 from "fs";
4552
- import path33 from "path";
4751
+ import fs30 from "fs";
4752
+ import path34 from "path";
4553
4753
  async function generateMinimaxImage(opts) {
4554
4754
  const {
4555
4755
  apiKey,
@@ -4587,8 +4787,8 @@ async function generateMinimaxImage(opts) {
4587
4787
  try {
4588
4788
  const bytes = await fetchBytes(url);
4589
4789
  const fname = `minimax_image_${Date.now()}_${i}.png`;
4590
- const dest = path33.join(outDir, fname);
4591
- fs28.writeFileSync(dest, bytes);
4790
+ const dest = path34.join(outDir, fname);
4791
+ fs30.writeFileSync(dest, bytes);
4592
4792
  savedFiles.push(dest);
4593
4793
  logger?.success(`Saved: ${dest}`);
4594
4794
  } catch (e) {
@@ -4599,15 +4799,15 @@ async function generateMinimaxImage(opts) {
4599
4799
  }
4600
4800
  }
4601
4801
  if (output && savedFiles[0]) {
4602
- fs28.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
4603
- fs28.copyFileSync(savedFiles[0], output);
4802
+ fs30.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
4803
+ fs30.copyFileSync(savedFiles[0], output);
4604
4804
  }
4605
4805
  return { status: "success", generatedImages: savedFiles, model };
4606
4806
  }
4607
4807
 
4608
4808
  // src/providers/minimax/commands/generate.ts
4609
4809
  function registerMinimaxGenerateCommand(parent) {
4610
- parent.command("generate").description("Generate images with MiniMax").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", `Model (${[...MINIMAX_IMAGE_MODELS].join("|")})`).option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
4810
+ parent.command("generate").description("Generate images with MiniMax").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", `Model (${[...MINIMAX_IMAGE_MODELS].join("|")})`).option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
4611
4811
  async (opts) => {
4612
4812
  const logger = createLogger({ verbose: opts.verbose ?? false });
4613
4813
  const apiKey = requireMinimaxKey();
@@ -4626,9 +4826,13 @@ function registerMinimaxGenerateCommand(parent) {
4626
4826
  logger.error(result.error ?? "Unknown error");
4627
4827
  process.exit(1);
4628
4828
  }
4829
+ const finalImages = finalizeGeneratedImages(result.generatedImages ?? [], {
4830
+ ...opts,
4831
+ logger
4832
+ });
4629
4833
  console.log(`
4630
- Generated ${result.generatedImages?.length ?? 0} image(s):`);
4631
- for (const f of result.generatedImages ?? []) console.log(` ${f}`);
4834
+ Generated ${finalImages.length} image(s):`);
4835
+ for (const f of finalImages) console.log(` ${f}`);
4632
4836
  }
4633
4837
  );
4634
4838
  }
@@ -4636,15 +4840,15 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
4636
4840
  // src/providers/minimax/commands/image-to-image.ts
4637
4841
  init_errors();
4638
4842
  init_http_client();
4639
- import fs29 from "fs";
4640
- import path34 from "path";
4843
+ import fs31 from "fs";
4844
+ import path35 from "path";
4641
4845
  function registerMinimaxImageToImageCommand(parent) {
4642
4846
  parent.command("image-to-image").alias("i2i").description(
4643
4847
  "MiniMax character/subject preservation. CAVEAT: NOT free-form image editing \u2014 the --ref image is used only as a character/face reference for a NEW scene described by --prompt."
4644
4848
  ).requiredOption("--prompt <text>", "Prompt describing the desired NEW scene/composition").requiredOption(
4645
4849
  "--ref <path>",
4646
4850
  "Subject reference image (URL or local path) \u2014 character/face reference only, single image"
4647
- ).option("--model <id>", "Model id (default image-01)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
4851
+ ).option("--model <id>", "Model id (default image-01)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
4648
4852
  async (opts) => {
4649
4853
  const logger = createLogger({ verbose: opts.verbose ?? false });
4650
4854
  const apiKey = requireMinimaxKey();
@@ -4692,8 +4896,8 @@ function registerMinimaxImageToImageCommand(parent) {
4692
4896
  if (!url) continue;
4693
4897
  try {
4694
4898
  const bytes = await fetchBytes(url);
4695
- const dest = path34.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4696
- fs29.writeFileSync(dest, bytes);
4899
+ const dest = path35.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4900
+ fs31.writeFileSync(dest, bytes);
4697
4901
  saved.push(dest);
4698
4902
  logger.success(`Saved: ${dest}`);
4699
4903
  } catch (e) {
@@ -4704,13 +4908,14 @@ function registerMinimaxImageToImageCommand(parent) {
4704
4908
  }
4705
4909
  }
4706
4910
  if (opts.output && saved[0]) {
4707
- fs29.mkdirSync(path34.dirname(path34.resolve(opts.output)), { recursive: true });
4708
- fs29.copyFileSync(saved[0], opts.output);
4911
+ fs31.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4912
+ fs31.copyFileSync(saved[0], opts.output);
4709
4913
  logger.success(`Copied to: ${opts.output}`);
4710
4914
  }
4915
+ const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
4711
4916
  console.log(`
4712
- Generated ${saved.length} image(s):`);
4713
- for (const f of saved) console.log(` ${f}`);
4917
+ Generated ${finalImages.length} image(s):`);
4918
+ for (const f of finalImages) console.log(` ${f}`);
4714
4919
  }
4715
4920
  );
4716
4921
  }
@@ -4839,12 +5044,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
4839
5044
 
4840
5045
  // src/providers/openai/openai-audio.ts
4841
5046
  init_http_client();
4842
- import fs31 from "fs";
4843
- import path36 from "path";
5047
+ import fs33 from "fs";
5048
+ import path37 from "path";
4844
5049
 
4845
5050
  // src/providers/openai/client.ts
4846
- import fs30 from "fs";
4847
- import path35 from "path";
5051
+ import fs32 from "fs";
5052
+ import path36 from "path";
4848
5053
  init_errors();
4849
5054
  init_http_client();
4850
5055
  var DEFAULT_TIMEOUT_MS3 = 24e4;
@@ -4911,10 +5116,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
4911
5116
  return Buffer.from(await res.arrayBuffer());
4912
5117
  }
4913
5118
  function fileFromPath(filePath) {
4914
- const abs = path35.resolve(filePath);
4915
- if (!fs30.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4916
- const bytes = fs30.readFileSync(abs);
4917
- return new File([new Uint8Array(bytes)], path35.basename(abs));
5119
+ const abs = path36.resolve(filePath);
5120
+ if (!fs32.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
5121
+ const bytes = fs32.readFileSync(abs);
5122
+ return new File([new Uint8Array(bytes)], path36.basename(abs));
4918
5123
  }
4919
5124
 
4920
5125
  // src/providers/openai/openai-audio.ts
@@ -4953,7 +5158,7 @@ async function resolveAudioReferenceDataUrl(ref) {
4953
5158
  }
4954
5159
  function audioMimeFromReference(ref) {
4955
5160
  const pathname = new URL(ref).pathname;
4956
- const ext = path36.extname(pathname).toLowerCase();
5161
+ const ext = path37.extname(pathname).toLowerCase();
4957
5162
  switch (ext) {
4958
5163
  case ".mp3":
4959
5164
  return "audio/mpeg";
@@ -4972,12 +5177,12 @@ function audioMimeFromReference(ref) {
4972
5177
  async function generateOpenAISpeech(opts) {
4973
5178
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
4974
5179
  const ext = extFromAudioFormat(opts.outputFormat);
4975
- const dest = path36.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4976
- fs31.writeFileSync(dest, bytes);
5180
+ const dest = path37.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
5181
+ fs33.writeFileSync(dest, bytes);
4977
5182
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
4978
5183
  if (opts.output) {
4979
- fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
4980
- fs31.copyFileSync(dest, opts.output);
5184
+ fs33.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
5185
+ fs33.copyFileSync(dest, opts.output);
4981
5186
  }
4982
5187
  return dest;
4983
5188
  }
@@ -4999,9 +5204,9 @@ async function transcribeOpenAIAudio(opts) {
4999
5204
  );
5000
5205
  const text = typeof data.text === "string" ? data.text : void 0;
5001
5206
  const ext = opts.format === "text" ? "txt" : "json";
5002
- const dest = opts.output ? path36.resolve(opts.output) : path36.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
5003
- fs31.mkdirSync(path36.dirname(dest), { recursive: true });
5004
- fs31.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
5207
+ const dest = opts.output ? path37.resolve(opts.output) : path37.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
5208
+ fs33.mkdirSync(path37.dirname(dest), { recursive: true });
5209
+ fs33.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
5005
5210
  opts.logger?.success(`Saved transcript: ${dest}`);
5006
5211
  return { path: dest, text };
5007
5212
  }
@@ -5028,9 +5233,9 @@ Generated audio: ${dest}`);
5028
5233
  }
5029
5234
 
5030
5235
  // src/providers/openai/codex-image-driver.ts
5031
- import fs32 from "fs";
5236
+ import fs34 from "fs";
5032
5237
  import os2 from "os";
5033
- import path37 from "path";
5238
+ import path38 from "path";
5034
5239
  import { execa } from "execa";
5035
5240
  init_errors();
5036
5241
  var defaultRunner = async (cmd, args, opts) => {
@@ -5079,12 +5284,12 @@ function sanitizeCodexEnv() {
5079
5284
  async function runCodexImageGeneration(opts) {
5080
5285
  const runner = opts.runner ?? defaultRunner;
5081
5286
  const ext = extFromImageFormat(opts.outputFormat);
5082
- const dest = opts.output ? path37.resolve(opts.output) : path37.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
5083
- fs32.mkdirSync(path37.dirname(dest), { recursive: true });
5084
- const tmp = fs32.mkdtempSync(path37.join(os2.tmpdir(), "multix-codex-image-"));
5085
- const schema = path37.join(tmp, "schema.json");
5086
- const result = path37.join(tmp, "result.json");
5087
- fs32.writeFileSync(
5287
+ const dest = opts.output ? path38.resolve(opts.output) : path38.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
5288
+ fs34.mkdirSync(path38.dirname(dest), { recursive: true });
5289
+ const tmp = fs34.mkdtempSync(path38.join(os2.tmpdir(), "multix-codex-image-"));
5290
+ const schema = path38.join(tmp, "schema.json");
5291
+ const result = path38.join(tmp, "result.json");
5292
+ fs34.writeFileSync(
5088
5293
  schema,
5089
5294
  JSON.stringify({
5090
5295
  type: "object",
@@ -5114,7 +5319,7 @@ async function runCodexImageGeneration(opts) {
5114
5319
  if (res.exitCode !== 0) {
5115
5320
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
5116
5321
  }
5117
- if (!fs32.existsSync(dest) || fs32.statSync(dest).size === 0) {
5322
+ if (!fs34.existsSync(dest) || fs34.statSync(dest).size === 0) {
5118
5323
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
5119
5324
  }
5120
5325
  return { path: dest };
@@ -5132,8 +5337,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
5132
5337
  // src/providers/openai/openai-image.ts
5133
5338
  init_errors();
5134
5339
  init_http_client();
5135
- import fs33 from "fs";
5136
- import path38 from "path";
5340
+ import fs35 from "fs";
5341
+ import path39 from "path";
5137
5342
  function buildImageGenerationBody(opts) {
5138
5343
  return {
5139
5344
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -5166,14 +5371,14 @@ function saveOpenAIImages(opts) {
5166
5371
  for (let i = 0; i < items.length; i++) {
5167
5372
  const b64 = items[i]?.b64_json;
5168
5373
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
5169
- const dest = path38.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
5170
- fs33.writeFileSync(dest, Buffer.from(b64, "base64"));
5374
+ const dest = path39.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
5375
+ fs35.writeFileSync(dest, Buffer.from(b64, "base64"));
5171
5376
  saved.push(dest);
5172
5377
  opts.logger?.success(`Saved: ${dest}`);
5173
5378
  }
5174
5379
  if (opts.output && saved[0]) {
5175
- fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5176
- fs33.copyFileSync(saved[0], opts.output);
5380
+ fs35.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5381
+ fs35.copyFileSync(saved[0], opts.output);
5177
5382
  opts.logger?.success(`Copied to: ${opts.output}`);
5178
5383
  }
5179
5384
  return saved;
@@ -5182,7 +5387,7 @@ async function resolveImageRefBytes(ref) {
5182
5387
  if (isHttpUrl(ref)) {
5183
5388
  const bytes2 = await fetchBytes(ref);
5184
5389
  const pathname = new URL(ref).pathname;
5185
- const filename = path38.basename(pathname) || "reference.png";
5390
+ const filename = path39.basename(pathname) || "reference.png";
5186
5391
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
5187
5392
  }
5188
5393
  const file = fileFromPath(ref);
@@ -5227,7 +5432,7 @@ function sniffImageMime2(buf, name) {
5227
5432
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
5228
5433
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
5229
5434
  return "image/webp";
5230
- const ext = path38.extname(name).toLowerCase();
5435
+ const ext = path39.extname(name).toLowerCase();
5231
5436
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
5232
5437
  if (ext === ".webp") return "image/webp";
5233
5438
  return "image/png";
@@ -5239,9 +5444,12 @@ function registerOpenAIGenerateCommand(parent) {
5239
5444
  "-m, --model <id>",
5240
5445
  "Image model",
5241
5446
  resolveKey("OPENAI_IMAGE_MODEL") ?? DEFAULT_OPENAI_IMAGE_MODEL
5242
- ).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option("--format <fmt>", "png|jpeg|webp", "png").option("-n, --num-images <n>", "Number of images for API driver", "1").option("--output <path>", "Save first image to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
5447
+ ).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option(
5448
+ "--format <fmt>",
5449
+ "Provider output format png|jpeg|webp (explicit value keeps it; default png then WebP)"
5450
+ ).option("-n, --num-images <n>", "Number of images for API driver", "1").option("--output <path>", "Save first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(async (opts) => {
5243
5451
  const logger = createLogger({ verbose: opts.verbose ?? false });
5244
- const outputFormat = parseImageFormat(opts.format);
5452
+ const outputFormat = parseImageFormat(opts.format ?? "png");
5245
5453
  const driver = await resolveImageDriver({ requested: parseDriver(opts.driver) });
5246
5454
  const saved = driver === "codex" ? [
5247
5455
  (await runCodexImageGeneration({
@@ -5259,9 +5467,14 @@ function registerOpenAIGenerateCommand(parent) {
5259
5467
  output: opts.output,
5260
5468
  logger
5261
5469
  });
5470
+ const finalImages = finalizeGeneratedImages(saved, {
5471
+ ...opts,
5472
+ logger,
5473
+ explicitProviderFormat: opts.format !== void 0
5474
+ });
5262
5475
  console.log(`
5263
- Generated ${saved.length} image(s):`);
5264
- for (const file of saved) console.log(` ${file}`);
5476
+ Generated ${finalImages.length} image(s):`);
5477
+ for (const f of finalImages) console.log(` ${f}`);
5265
5478
  });
5266
5479
  }
5267
5480
 
@@ -5277,12 +5490,15 @@ function registerOpenAIImageToImageCommand(parent) {
5277
5490
  "-m, --model <id>",
5278
5491
  "Image model",
5279
5492
  resolveKey("OPENAI_IMAGE_MODEL") ?? DEFAULT_OPENAI_IMAGE_MODEL
5280
- ).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option("--format <fmt>", "png|jpeg|webp", "png").option("--output <path>", "Save first image to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
5493
+ ).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option(
5494
+ "--format <fmt>",
5495
+ "Provider output format png|jpeg|webp (explicit value keeps it; default png then WebP)"
5496
+ ).option("--output <path>", "Save first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(async (opts) => {
5281
5497
  if (!opts.ref || opts.ref.length === 0) {
5282
5498
  throw new ValidationError("At least one --ref is required for image-to-image.");
5283
5499
  }
5284
5500
  const logger = createLogger({ verbose: opts.verbose ?? false });
5285
- const outputFormat = parseImageFormat(opts.format);
5501
+ const outputFormat = parseImageFormat(opts.format ?? "png");
5286
5502
  const driver = await resolveImageDriver({ requested: parseDriver(opts.driver) });
5287
5503
  const saved = driver === "codex" ? [
5288
5504
  (await runCodexImageGeneration({
@@ -5301,9 +5517,14 @@ function registerOpenAIImageToImageCommand(parent) {
5301
5517
  output: opts.output,
5302
5518
  logger
5303
5519
  });
5520
+ const finalImages = finalizeGeneratedImages(saved, {
5521
+ ...opts,
5522
+ logger,
5523
+ explicitProviderFormat: opts.format !== void 0
5524
+ });
5304
5525
  console.log(`
5305
- Generated ${saved.length} image(s):`);
5306
- for (const file of saved) console.log(` ${file}`);
5526
+ Generated ${finalImages.length} image(s):`);
5527
+ for (const f of finalImages) console.log(` ${f}`);
5307
5528
  });
5308
5529
  }
5309
5530
  function collect5(value, prev) {
@@ -5355,8 +5576,8 @@ function registerOpenAICommands(program2) {
5355
5576
  }
5356
5577
 
5357
5578
  // src/providers/openrouter/client.ts
5358
- import fs34 from "fs";
5359
- import path39 from "path";
5579
+ import fs36 from "fs";
5580
+ import path40 from "path";
5360
5581
  init_errors();
5361
5582
  init_http_client();
5362
5583
 
@@ -5521,8 +5742,8 @@ async function generateOpenRouterImage(opts) {
5521
5742
  if (!url) continue;
5522
5743
  try {
5523
5744
  const bytes = await fetchBytes(url);
5524
- const dest = path39.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5525
- fs34.writeFileSync(dest, bytes);
5745
+ const dest = path40.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5746
+ fs36.writeFileSync(dest, bytes);
5526
5747
  savedFiles.push(dest);
5527
5748
  logger?.success(`Saved: ${dest}`);
5528
5749
  } catch (e) {
@@ -5533,8 +5754,8 @@ async function generateOpenRouterImage(opts) {
5533
5754
  }
5534
5755
  }
5535
5756
  if (output && savedFiles[0]) {
5536
- fs34.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
5537
- fs34.writeFileSync(output, fs34.readFileSync(savedFiles[0]));
5757
+ fs36.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
5758
+ fs36.writeFileSync(output, fs36.readFileSync(savedFiles[0]));
5538
5759
  }
5539
5760
  return { status: "success", generatedImages: savedFiles, model: usedModel };
5540
5761
  }
@@ -5543,7 +5764,7 @@ async function generateOpenRouterImage(opts) {
5543
5764
  function registerOpenRouterGenerateCommand(parent) {
5544
5765
  parent.command("generate").description(
5545
5766
  "Generate images via OpenRouter (text-to-image); models: Gemini, GPT-Image, Flux, Recraft, Seedream"
5546
- ).requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "OpenRouter model id (e.g. google/gemini-3.1-flash-image-preview)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--image-size <size>", "Image size hint (e.g. 1K, 2K)").option("--num-images <n>", "Number of images to generate", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
5767
+ ).requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "OpenRouter model id (e.g. google/gemini-3.1-flash-image-preview)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--image-size <size>", "Image size hint (e.g. 1K, 2K)").option("--num-images <n>", "Number of images to generate", "1").option("--output <path>", "Copy first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
5547
5768
  async (opts) => {
5548
5769
  const logger = createLogger({ verbose: opts.verbose ?? false });
5549
5770
  const model = opts.model ?? resolveKey("OPENROUTER_IMAGE_MODEL") ?? DEFAULT_OPENROUTER_MODEL;
@@ -5562,17 +5783,21 @@ function registerOpenRouterGenerateCommand(parent) {
5562
5783
  logger.error(result.error ?? "Unknown error");
5563
5784
  process.exit(1);
5564
5785
  }
5786
+ const finalImages = finalizeGeneratedImages(result.generatedImages ?? [], {
5787
+ ...opts,
5788
+ logger
5789
+ });
5565
5790
  console.log(`
5566
- Generated ${result.generatedImages?.length ?? 0} image(s):`);
5567
- for (const f of result.generatedImages ?? []) console.log(` ${f}`);
5791
+ Generated ${finalImages.length} image(s):`);
5792
+ for (const f of finalImages) console.log(` ${f}`);
5568
5793
  if (result.model) console.log(`Model used: ${result.model}`);
5569
5794
  }
5570
5795
  );
5571
5796
  }
5572
5797
 
5573
5798
  // src/providers/openrouter/commands/image-to-image.ts
5574
- import fs35 from "fs";
5575
- import path40 from "path";
5799
+ import fs37 from "fs";
5800
+ import path41 from "path";
5576
5801
  init_errors();
5577
5802
  init_http_client();
5578
5803
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
@@ -5586,7 +5811,7 @@ function registerOpenRouterImageToImageCommand(parent) {
5586
5811
  "Reference image (URL or local path); repeatable (most models accept 1)",
5587
5812
  collect7,
5588
5813
  []
5589
- ).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--strength <n>", "Recraft init-image strength 0..1 (Recraft models only)").option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
5814
+ ).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--strength <n>", "Recraft init-image strength 0..1 (Recraft models only)").option("--output <path>", "Save first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
5590
5815
  async (opts) => {
5591
5816
  if (!opts.ref || opts.ref.length === 0) {
5592
5817
  throw new ValidationError("At least one --ref is required for image-to-image.");
@@ -5618,9 +5843,10 @@ function registerOpenRouterImageToImageCommand(parent) {
5618
5843
  output: opts.output,
5619
5844
  logger
5620
5845
  });
5846
+ const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
5621
5847
  console.log(`
5622
- Generated ${saved.length} image(s):`);
5623
- for (const f of saved) console.log(` ${f}`);
5848
+ Generated ${finalImages.length} image(s):`);
5849
+ for (const f of finalImages) console.log(` ${f}`);
5624
5850
  console.log(`Model used: ${model}`);
5625
5851
  }
5626
5852
  );
@@ -5664,14 +5890,14 @@ async function runImageToImage(opts) {
5664
5890
  const url = parsed.urls[i];
5665
5891
  if (!url) continue;
5666
5892
  const bytes = await fetchBytes(url);
5667
- const dest = path40.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5668
- fs35.writeFileSync(dest, bytes);
5893
+ const dest = path41.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5894
+ fs37.writeFileSync(dest, bytes);
5669
5895
  saved.push(dest);
5670
5896
  opts.logger.success(`Saved: ${dest}`);
5671
5897
  }
5672
5898
  if (opts.output && saved[0]) {
5673
- fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5674
- fs35.copyFileSync(saved[0], opts.output);
5899
+ fs37.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
5900
+ fs37.copyFileSync(saved[0], opts.output);
5675
5901
  opts.logger.success(`Copied to: ${opts.output}`);
5676
5902
  }
5677
5903
  return saved;
@@ -5717,13 +5943,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
5717
5943
  }
5718
5944
 
5719
5945
  // src/providers/openrouter/commands/video-status.ts
5720
- import fs36 from "fs";
5721
- import path41 from "path";
5946
+ import fs38 from "fs";
5947
+ import path42 from "path";
5722
5948
  async function downloadOpenRouterVideo(opts) {
5723
5949
  const { jobId, logger, source } = opts;
5724
5950
  const apiKey = requireOpenRouterKey();
5725
5951
  const url = videoContentUrl(jobId);
5726
- const dest = path41.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5952
+ const dest = path42.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5727
5953
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
5728
5954
  if (!r.ok) {
5729
5955
  logger.error(`Download HTTP ${r.status}`);
@@ -5733,14 +5959,14 @@ async function downloadOpenRouterVideo(opts) {
5733
5959
  logger.error("Empty response body");
5734
5960
  process.exit(1);
5735
5961
  }
5736
- fs36.mkdirSync(path41.dirname(dest), { recursive: true });
5737
- const ws = fs36.createWriteStream(dest);
5962
+ fs38.mkdirSync(path42.dirname(dest), { recursive: true });
5963
+ const ws = fs38.createWriteStream(dest);
5738
5964
  const { pipeline: pipeline2 } = await import("stream/promises");
5739
5965
  await pipeline2(r.body, ws);
5740
5966
  logger.success(`Saved ${dest}`);
5741
5967
  if (opts.output) {
5742
- fs36.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
5743
- fs36.copyFileSync(dest, opts.output);
5968
+ fs38.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
5969
+ fs38.copyFileSync(dest, opts.output);
5744
5970
  logger.success(`Copied to ${opts.output}`);
5745
5971
  }
5746
5972
  await maybeDownloadThumb(source, dest, {
@@ -6151,8 +6377,8 @@ function registerCheckCommand(program2) {
6151
6377
 
6152
6378
  // src/commands/doc/convert.ts
6153
6379
  init_errors();
6154
- import fs37 from "fs";
6155
- import path42 from "path";
6380
+ import fs39 from "fs";
6381
+ import path43 from "path";
6156
6382
 
6157
6383
  // src/commands/doc/default-prompt.ts
6158
6384
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -6173,18 +6399,18 @@ async function convertDocuments(opts) {
6173
6399
  const model = opts.model ?? DOC_MODEL_DEFAULT;
6174
6400
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
6175
6401
  for (const f of inputs) {
6176
- if (!fs37.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
6402
+ if (!fs39.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
6177
6403
  }
6178
6404
  let outputPath;
6179
6405
  if (output) {
6180
- outputPath = path42.resolve(output);
6406
+ outputPath = path43.resolve(output);
6181
6407
  } else if (autoName && inputs.length === 1 && inputs[0]) {
6182
- const base = path42.basename(inputs[0], path42.extname(inputs[0]));
6183
- outputPath = path42.join(getOutputDir(), `${base}-extraction.md`);
6408
+ const base = path43.basename(inputs[0], path43.extname(inputs[0]));
6409
+ outputPath = path43.join(getOutputDir(), `${base}-extraction.md`);
6184
6410
  } else {
6185
- outputPath = path42.join(getOutputDir(), "document-extraction.md");
6411
+ outputPath = path43.join(getOutputDir(), "document-extraction.md");
6186
6412
  }
6187
- fs37.mkdirSync(path42.dirname(outputPath), { recursive: true });
6413
+ fs39.mkdirSync(path43.dirname(outputPath), { recursive: true });
6188
6414
  const results = [];
6189
6415
  for (let i = 0; i < inputs.length; i++) {
6190
6416
  const filePath = inputs[i];
@@ -6235,7 +6461,7 @@ async function convertDocuments(opts) {
6235
6461
  `);
6236
6462
  for (const r of results) {
6237
6463
  lines.push(`
6238
- ## ${path42.basename(r.file)}
6464
+ ## ${path43.basename(r.file)}
6239
6465
  `);
6240
6466
  if (r.status === "success" && r.markdown) {
6241
6467
  lines.push(`${r.markdown}
@@ -6248,7 +6474,7 @@ async function convertDocuments(opts) {
6248
6474
  }
6249
6475
  lines.push("\n---\n");
6250
6476
  }
6251
- fs37.writeFileSync(outputPath, lines.join("\n"), "utf8");
6477
+ fs39.writeFileSync(outputPath, lines.join("\n"), "utf8");
6252
6478
  const succeeded = results.filter((r) => r.status === "success").length;
6253
6479
  console.log(`
6254
6480
  ${"=".repeat(50)}`);
@@ -6296,22 +6522,22 @@ function registerDocCommands(program2) {
6296
6522
 
6297
6523
  // src/commands/media/index.ts
6298
6524
  init_errors();
6299
- import fs40 from "fs";
6300
- import path45 from "path";
6525
+ import fs42 from "fs";
6526
+ import path46 from "path";
6301
6527
 
6302
6528
  // src/commands/media/batch.ts
6303
- import fs38 from "fs";
6304
- import path43 from "path";
6529
+ import fs40 from "fs";
6530
+ import path44 from "path";
6305
6531
 
6306
6532
  // src/commands/media/detect-type.ts
6307
6533
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
6308
6534
  var AUDIO_EXTS = /* @__PURE__ */ new Set([".mp3", ".wav", ".m4a", ".flac", ".aac", ".ogg", ".aiff"]);
6309
- var IMAGE_EXTS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif", ".gif", ".bmp"]);
6535
+ var IMAGE_EXTS2 = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif", ".gif", ".bmp"]);
6310
6536
  function extToKind(ext) {
6311
6537
  const lower = ext.toLowerCase();
6312
6538
  if (VIDEO_EXTS.has(lower)) return "video";
6313
6539
  if (AUDIO_EXTS.has(lower)) return "audio";
6314
- if (IMAGE_EXTS.has(lower)) return "image";
6540
+ if (IMAGE_EXTS2.has(lower)) return "image";
6315
6541
  return void 0;
6316
6542
  }
6317
6543
 
@@ -6486,7 +6712,7 @@ async function optimizeVideo(opts) {
6486
6712
  }
6487
6713
 
6488
6714
  // src/commands/media/batch.ts
6489
- var ALL_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS, ...IMAGE_EXTS]);
6715
+ var ALL_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS, ...IMAGE_EXTS2]);
6490
6716
  async function batchOptimize(opts) {
6491
6717
  const {
6492
6718
  inputDir,
@@ -6497,10 +6723,10 @@ async function batchOptimize(opts) {
6497
6723
  verbose,
6498
6724
  logger
6499
6725
  } = opts;
6500
- fs38.mkdirSync(outputDir, { recursive: true });
6501
- const entries = fs38.readdirSync(inputDir);
6726
+ fs40.mkdirSync(outputDir, { recursive: true });
6727
+ const entries = fs40.readdirSync(inputDir);
6502
6728
  const mediaFiles = entries.filter((f) => {
6503
- const ext = path43.extname(f).toLowerCase();
6729
+ const ext = path44.extname(f).toLowerCase();
6504
6730
  return ALL_EXTS.has(ext);
6505
6731
  });
6506
6732
  if (mediaFiles.length === 0) {
@@ -6511,9 +6737,9 @@ async function batchOptimize(opts) {
6511
6737
  let succeeded = 0;
6512
6738
  const errors = [];
6513
6739
  for (const file of mediaFiles) {
6514
- const inputPath = path43.join(inputDir, file);
6515
- const outputPath = path43.join(outputDir, file);
6516
- const ext = path43.extname(file).toLowerCase();
6740
+ const inputPath = path44.join(inputDir, file);
6741
+ const outputPath = path44.join(outputDir, file);
6742
+ const ext = path44.extname(file).toLowerCase();
6517
6743
  const kind = extToKind(ext);
6518
6744
  logger?.debug(`Processing: ${file}`);
6519
6745
  try {
@@ -6550,8 +6776,8 @@ async function batchOptimize(opts) {
6550
6776
 
6551
6777
  // src/commands/media/split-video.ts
6552
6778
  init_errors();
6553
- import fs39 from "fs";
6554
- import path44 from "path";
6779
+ import fs41 from "fs";
6780
+ import path45 from "path";
6555
6781
  async function splitVideo(opts) {
6556
6782
  await assertFfmpeg();
6557
6783
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -6562,10 +6788,10 @@ async function splitVideo(opts) {
6562
6788
  logger?.info("Video is short enough \u2014 no splitting needed");
6563
6789
  return [input];
6564
6790
  }
6565
- fs39.mkdirSync(outputDir, { recursive: true });
6566
- const ext = path44.extname(input) || ".mp4";
6567
- const baseName = path44.basename(input, ext);
6568
- const outputPattern = path44.join(outputDir, `${baseName}_chunk_%03d${ext}`);
6791
+ fs41.mkdirSync(outputDir, { recursive: true });
6792
+ const ext = path45.extname(input) || ".mp4";
6793
+ const baseName = path45.basename(input, ext);
6794
+ const outputPattern = path45.join(outputDir, `${baseName}_chunk_%03d${ext}`);
6569
6795
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
6570
6796
  const args = [
6571
6797
  "-i",
@@ -6584,8 +6810,8 @@ async function splitVideo(opts) {
6584
6810
  await runFfmpeg(args, { verbose, logger });
6585
6811
  const files = [];
6586
6812
  for (let i = 0; i < numChunks + 1; i++) {
6587
- const fname = path44.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6588
- if (fs39.existsSync(fname)) files.push(fname);
6813
+ const fname = path45.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6814
+ if (fs41.existsSync(fname)) files.push(fname);
6589
6815
  }
6590
6816
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
6591
6817
  return files;
@@ -6599,9 +6825,9 @@ function registerMediaCommands(program2) {
6599
6825
  media.command("optimize").description("Optimize a single media file (video, audio, or image)").requiredOption("--input <file>", "Input media file").requiredOption("--output <file>", "Output file path").option("--target-size <MB>", "Target file size in MB").option("--quality <n>", "Quality: video CRF (0-51) or image (1-100)", "85").option("--max-width <px>", "Max image/video width in pixels", "1920").option("--bitrate <rate>", "Audio bitrate (e.g. 64k)", "64k").option("--resolution <WxH>", "Force video resolution (e.g. 1920x1080)").option("-v, --verbose", "Verbose logging").action(
6600
6826
  async (opts) => {
6601
6827
  const logger = createLogger({ verbose: opts.verbose ?? false });
6602
- if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6603
- fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
6604
- const ext = path45.extname(opts.input).toLowerCase();
6828
+ if (!fs42.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6829
+ fs42.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
6830
+ const ext = path46.extname(opts.input).toLowerCase();
6605
6831
  const kind = extToKind(ext);
6606
6832
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
6607
6833
  const quality = Number.parseInt(opts.quality, 10);
@@ -6641,7 +6867,7 @@ function registerMediaCommands(program2) {
6641
6867
  media.command("split").description("Split a long video into fixed-duration chunks").requiredOption("--input <file>", "Input video file").option("--output-dir <dir>", "Output directory for chunks", "./chunks").option("--chunk-duration <sec>", "Chunk duration in seconds", "3600").option("-v, --verbose", "Verbose logging").action(
6642
6868
  async (opts) => {
6643
6869
  const logger = createLogger({ verbose: opts.verbose ?? false });
6644
- if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6870
+ if (!fs42.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6645
6871
  const chunks = await splitVideo({
6646
6872
  input: opts.input,
6647
6873
  outputDir: opts.outputDir,
@@ -6657,7 +6883,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
6657
6883
  media.command("batch").description("Batch optimize all media files in a directory").requiredOption("--input-dir <dir>", "Input directory").requiredOption("--output-dir <dir>", "Output directory").option("--quality <n>", "Quality setting", "85").option("--max-width <px>", "Max image/video width", "1920").option("--bitrate <rate>", "Audio bitrate", "64k").option("-v, --verbose", "Verbose logging").action(
6658
6884
  async (opts) => {
6659
6885
  const logger = createLogger({ verbose: opts.verbose ?? false });
6660
- if (!fs40.existsSync(opts.inputDir))
6886
+ if (!fs42.existsSync(opts.inputDir))
6661
6887
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
6662
6888
  const result = await batchOptimize({
6663
6889
  inputDir: opts.inputDir,