@mrgoonie/multix 1.0.1 → 1.0.3

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
@@ -307,6 +307,7 @@ function validateRawInput(spec, body) {
307
307
  }
308
308
 
309
309
  // src/providers/atlascloud/models.ts
310
+ var ATLAS_PROVIDER = "atlascloud";
310
311
  var DEFAULT_ATLAS_BASE_URL = "https://api.atlascloud.ai/api/v1";
311
312
  var ATLAS_ROUTING = {
312
313
  name: "ATLASCLOUD_BASE_URL",
@@ -626,6 +627,134 @@ var defaultLogger = createLogger({ verbose: false });
626
627
  // src/providers/atlascloud/commands/env.ts
627
628
  init_redact();
628
629
 
630
+ // src/core/url-safety.ts
631
+ init_errors();
632
+ init_redact();
633
+ function ipv4Octets(host) {
634
+ const parts = host.split(".");
635
+ if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
636
+ const octets = parts.map(Number);
637
+ return octets.every((o) => o <= 255) ? octets : void 0;
638
+ }
639
+ function isNonPublicIpv4([a = 0, b = 0, c2 = 0]) {
640
+ return a === 0 || // "this network" / unspecified
641
+ a === 10 || a === 127 || // loopback
642
+ a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
643
+ a === 169 && b === 254 || // link-local (cloud metadata)
644
+ a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 && c2 === 0 || // IETF protocol assignments
645
+ a === 192 && b === 0 && c2 === 2 || // TEST-NET-1
646
+ a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || // benchmarking
647
+ a === 198 && b === 51 && c2 === 100 || // TEST-NET-2
648
+ a === 203 && b === 0 && c2 === 113 || // TEST-NET-3
649
+ a >= 224;
650
+ }
651
+ function ipv6Groups(host) {
652
+ if (!/^[0-9a-f:.]+$/i.test(host) || !host.includes(":")) return void 0;
653
+ let text = host;
654
+ const tail = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
655
+ if (tail) {
656
+ const octets = ipv4Octets(tail[2]);
657
+ if (!octets) return void 0;
658
+ const [a = 0, b = 0, c2 = 0, d = 0] = octets;
659
+ text = `${tail[1]}${(a << 8 | b).toString(16)}:${(c2 << 8 | d).toString(16)}`;
660
+ }
661
+ const halves = text.split("::");
662
+ if (halves.length > 2) return void 0;
663
+ const parse = (s) => s === "" ? [] : s.split(":");
664
+ const head = parse(halves[0]);
665
+ const rest = halves.length === 2 ? parse(halves[1]) : [];
666
+ const missing = 8 - head.length - rest.length;
667
+ if (halves.length === 2 ? missing < 1 : missing !== 0) return void 0;
668
+ const all = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...rest];
669
+ if (!all.every((g) => /^[0-9a-f]{1,4}$/i.test(g))) return void 0;
670
+ return all.map((g) => Number.parseInt(g, 16));
671
+ }
672
+ function embeddedIpv4(hi, lo) {
673
+ return [hi >> 8, hi & 255, lo >> 8, lo & 255];
674
+ }
675
+ function isNonPublicIpv6(groups) {
676
+ const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
677
+ const zeroUpTo = (n) => groups.slice(0, n).every((g) => g === 0);
678
+ if (zeroUpTo(5) && g5 === 65535) return isNonPublicIpv4(embeddedIpv4(g6, g7));
679
+ if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
680
+ return isNonPublicIpv4(embeddedIpv4(g6, g7));
681
+ }
682
+ if (g0 === 8194) return isNonPublicIpv4(embeddedIpv4(g1, g2));
683
+ return g0 === 0 || // unspecified (::), loopback (::1), IPv4-compatible and other reserved
684
+ g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0 || // discard-only 100::/64
685
+ (g0 & 65024) === 64512 || // unique local fc00::/7
686
+ (g0 & 65472) === 65152 || // link-local fe80::/10
687
+ (g0 & 65472) === 65216 || // deprecated site-local fec0::/10
688
+ (g0 & 65280) === 65280;
689
+ }
690
+ function bareHost(hostname) {
691
+ const host = hostname.toLowerCase().replace(/\.$/, "");
692
+ return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
693
+ }
694
+ function isLoopbackHost(hostname) {
695
+ const host = bareHost(hostname);
696
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
697
+ const octets = ipv4Octets(host);
698
+ if (octets) return octets[0] === 127;
699
+ const groups = ipv6Groups(host);
700
+ if (!groups) return false;
701
+ if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535) {
702
+ return groups[6] !== void 0 && groups[6] >> 8 === 127;
703
+ }
704
+ return groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1;
705
+ }
706
+ function isNonPublicHost(hostname) {
707
+ const host = bareHost(hostname);
708
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
709
+ const octets = ipv4Octets(host);
710
+ if (octets) return isNonPublicIpv4(octets);
711
+ const groups = ipv6Groups(host);
712
+ return groups !== void 0 && isNonPublicIpv6(groups);
713
+ }
714
+ var LOOPBACK_BASE_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
715
+ function checkBaseUrl(raw, variable) {
716
+ const value = raw.trim();
717
+ let url;
718
+ try {
719
+ url = new URL(value);
720
+ } catch {
721
+ throw new ConfigError(`${variable} must be an absolute https:// URL`);
722
+ }
723
+ const loopbackHttp = url.protocol === "http:" && LOOPBACK_BASE_HOSTS.has(url.hostname);
724
+ if (url.protocol !== "https:" && !loopbackHttp) {
725
+ throw new ConfigError(
726
+ `${variable} must use https:// (got ${redactUrl(value)}); plain http:// is only allowed for localhost, 127.0.0.1, or [::1]`
727
+ );
728
+ }
729
+ return value;
730
+ }
731
+ function checkUrlSafety(url, opts) {
732
+ const action = opts.action ?? "download from";
733
+ let parsed;
734
+ try {
735
+ parsed = new URL(url);
736
+ } catch {
737
+ throw new ProviderError(`Refusing to ${action} a malformed URL`, opts.provider);
738
+ }
739
+ const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
740
+ const localOk = opts.allowLocalhostHttp === true && isLoopbackHost(host);
741
+ const httpOk = parsed.protocol === "https:" || localOk && parsed.protocol === "http:";
742
+ if (!httpOk) {
743
+ throw new ProviderError(
744
+ `Refusing to ${action} ${host || "this URL"}: only https:// URLs are allowed`,
745
+ opts.provider
746
+ );
747
+ }
748
+ if (!localOk && isNonPublicHost(host)) {
749
+ throw new ProviderError(
750
+ `Refusing to ${action} ${host}: it points to a local or private address`,
751
+ opts.provider
752
+ );
753
+ }
754
+ const trusted = (opts.trustedHosts ?? []).some((t) => host === t || host.endsWith(`.${t}`));
755
+ return { host, trusted };
756
+ }
757
+
629
758
  // src/core/async-poll.ts
630
759
  init_errors();
631
760
  var PollTimeoutError = class extends MultixError {
@@ -868,6 +997,7 @@ async function fetchJson(opts) {
868
997
  }
869
998
 
870
999
  // src/providers/atlascloud/prediction-output.ts
1000
+ init_errors();
871
1001
  var SUCCESS_STATUSES = /* @__PURE__ */ new Set(["completed", "succeeded", "success"]);
872
1002
  var FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "canceled", "cancelled", "timeout"]);
873
1003
  function norm(status) {
@@ -917,6 +1047,9 @@ function collectOutputs(prediction) {
917
1047
  }
918
1048
  return out;
919
1049
  }
1050
+ function checkOutputUrl(url) {
1051
+ checkUrlSafety(url, { provider: ATLAS_PROVIDER });
1052
+ }
920
1053
  function isMediaOutput(value) {
921
1054
  const s = value.trim();
922
1055
  return /^https?:\/\//i.test(s) || /^data:[^,]*,/i.test(s);
@@ -941,6 +1074,7 @@ var EXT_CATEGORY = {
941
1074
  flac: "audio",
942
1075
  ogg: "audio",
943
1076
  opus: "audio",
1077
+ weba: "audio",
944
1078
  pcm: "audio",
945
1079
  glb: "3d",
946
1080
  gltf: "3d",
@@ -958,8 +1092,14 @@ var MIME_EXT = {
958
1092
  "image/jpeg": "jpg",
959
1093
  "image/webp": "webp",
960
1094
  "image/gif": "gif",
1095
+ "image/avif": "avif",
1096
+ "image/bmp": "bmp",
1097
+ "image/svg+xml": "svg",
961
1098
  "video/mp4": "mp4",
962
1099
  "video/webm": "webm",
1100
+ "video/x-matroska": "mkv",
1101
+ "video/matroska": "mkv",
1102
+ "audio/webm": "weba",
963
1103
  "audio/mpeg": "mp3",
964
1104
  "audio/mp3": "mp3",
965
1105
  "audio/wav": "wav",
@@ -976,6 +1116,7 @@ function extensionForContentType(contentType) {
976
1116
  const mime = contentType?.split(";")[0]?.trim().toLowerCase();
977
1117
  return mime ? MIME_EXT[mime] : void 0;
978
1118
  }
1119
+ var KNOWN_EXTENSIONS = /* @__PURE__ */ new Set([...Object.keys(EXT_CATEGORY), "svg"]);
979
1120
  function outputExtension(value) {
980
1121
  const s = value.trim();
981
1122
  const dataMatch = /^data:([^;,]+)/i.exec(s);
@@ -989,6 +1130,7 @@ function outputExtension(value) {
989
1130
  const m = /\.([a-z0-9]{2,5})$/i.exec(pathname);
990
1131
  if (!m?.[1]) return void 0;
991
1132
  const ext = m[1].toLowerCase();
1133
+ if (!KNOWN_EXTENSIONS.has(ext)) return void 0;
992
1134
  return ext === "jpeg" ? "jpg" : ext;
993
1135
  }
994
1136
  function outputCategory(value) {
@@ -1022,13 +1164,32 @@ function classifyOutputs(prediction) {
1022
1164
  function hasNsfwFlag(prediction) {
1023
1165
  return Array.isArray(prediction.has_nsfw_contents) && prediction.has_nsfw_contents.some(Boolean);
1024
1166
  }
1167
+ function thumbnailSource(prediction) {
1168
+ return prediction.outputs ?? prediction.output ?? null;
1169
+ }
1170
+ var MAX_PREDICTION_ID_LENGTH = 128;
1171
+ var NAME_ID_LENGTH = 64;
1172
+ function fnv1a(value) {
1173
+ let hash = 2166136261;
1174
+ for (let i = 0; i < value.length; i++) {
1175
+ hash ^= value.charCodeAt(i);
1176
+ hash = Math.imul(hash, 16777619) >>> 0;
1177
+ }
1178
+ return hash.toString(16).padStart(8, "0");
1179
+ }
1025
1180
  function outputFileName(predictionId, index, ext) {
1026
- const prefix = predictionId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 8) || "output";
1027
- return `atlas-${prefix}-${String(index + 1).padStart(2, "0")}.${ext}`;
1181
+ if (predictionId.length === 0 || predictionId.length > MAX_PREDICTION_ID_LENGTH) {
1182
+ throw new ProviderError(
1183
+ `Atlas Cloud returned an unusable prediction id (${predictionId.length === 0 ? "empty" : `${predictionId.length} characters`}).`,
1184
+ ATLAS_PROVIDER
1185
+ );
1186
+ }
1187
+ const short = predictionId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, NAME_ID_LENGTH);
1188
+ const id = short === predictionId ? short : short ? `${short}-${fnv1a(predictionId)}` : fnv1a(predictionId);
1189
+ return `atlas-${id}-${String(index + 1).padStart(2, "0")}.${ext}`;
1028
1190
  }
1029
1191
 
1030
1192
  // src/providers/atlascloud/client.ts
1031
- var ATLAS_PROVIDER = "atlascloud";
1032
1193
  var DEFAULT_UPLOAD_TIMEOUT_MS = 3e5;
1033
1194
  var CATALOG_TIMEOUT_MS = 15e3;
1034
1195
  var PREDICTION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
@@ -1122,13 +1283,13 @@ var AtlasCloudClient = class {
1122
1283
  }
1123
1284
  /** Submit a generation job. `body` holds `model` plus the model's own fields, flattened. */
1124
1285
  async submit(kind, body) {
1125
- const path57 = SUBMIT_PATHS[kind];
1126
- if (!path57) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
1286
+ const path58 = SUBMIT_PATHS[kind];
1287
+ if (!path58) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
1127
1288
  if (typeof body.model !== "string" || !body.model.trim()) {
1128
1289
  throw new ValidationError("Atlas Cloud request body needs a non-empty `model`");
1129
1290
  }
1130
1291
  const env = await fetchJson({
1131
- url: `${this.baseUrl}${path57}`,
1292
+ url: `${this.baseUrl}${path58}`,
1132
1293
  method: "POST",
1133
1294
  headers: this.authHeaders(),
1134
1295
  body,
@@ -1316,7 +1477,7 @@ function requireAtlasKey() {
1316
1477
  }
1317
1478
  function createAtlasClientFromEnv() {
1318
1479
  const apiKey = requireAtlasKey();
1319
- const baseUrl = resolveProviderBaseUrl(ATLAS_ROUTING);
1480
+ const baseUrl = checkBaseUrl(resolveProviderBaseUrl(ATLAS_ROUTING), "ATLASCLOUD_BASE_URL");
1320
1481
  return new AtlasCloudClient({ apiKey, baseUrl });
1321
1482
  }
1322
1483
  async function resolveMediaRef(client, ref, logger) {
@@ -1386,8 +1547,8 @@ async function resolveMediaRefs(client, refs, logger) {
1386
1547
 
1387
1548
  // src/providers/atlascloud/commands/prediction-runner.ts
1388
1549
  init_errors();
1389
- import fs8 from "fs";
1390
- import path7 from "path";
1550
+ import fs9 from "fs";
1551
+ import path8 from "path";
1391
1552
 
1392
1553
  // src/core/http-client.ts
1393
1554
  import fs3 from "fs";
@@ -1787,14 +1948,18 @@ function detectThumbUrl(obj) {
1787
1948
  }
1788
1949
  return null;
1789
1950
  }
1951
+ function resultDest(result) {
1952
+ const dest = result?.dest;
1953
+ return typeof dest === "string" ? dest : void 0;
1954
+ }
1790
1955
  async function downloadThumbBeside(thumbUrl, videoPath, logger, download = downloadFile) {
1791
1956
  const match = thumbUrl.match(IMAGE_EXT_RE);
1792
1957
  const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
1793
1958
  const dir = path6.dirname(videoPath);
1794
1959
  const base = path6.basename(videoPath, path6.extname(videoPath));
1795
- const dest = path6.join(dir, `${base}_thumb.${ext}`);
1960
+ const planned = path6.join(dir, `${base}_thumb.${ext}`);
1796
1961
  try {
1797
- await download(thumbUrl, dest);
1962
+ const dest = resultDest(await download(thumbUrl, planned)) ?? planned;
1798
1963
  logger?.success(`Saved thumb ${dest}`);
1799
1964
  return dest;
1800
1965
  } catch (e) {
@@ -1822,6 +1987,189 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
1822
1987
  return saved;
1823
1988
  }
1824
1989
 
1990
+ // src/providers/atlascloud/commands/output-claims.ts
1991
+ init_errors();
1992
+ import fs8 from "fs";
1993
+ import os2 from "os";
1994
+ import path7 from "path";
1995
+ var PART = ".part";
1996
+ var PART_SUFFIX = /(?:\.\d+-\d+-[a-z0-9]+)?\.part$/i;
1997
+ var TEMP_TAIL = /^\.(\d+)-\d+-[a-z0-9]+\.part$/i;
1998
+ var MAX_UNIQUE_ATTEMPTS = 1e3;
1999
+ var UNREADABLE_LOCK_GRACE_MS = 1e4;
2000
+ var foldCase = process.platform === "win32" || process.platform === "darwin";
2001
+ var fold = (s) => foldCase ? s.toLowerCase() : s;
2002
+ function stemOf(name) {
2003
+ const base = name.replace(PART_SUFFIX, "");
2004
+ return fold(base.slice(0, base.length - path7.extname(base).length));
2005
+ }
2006
+ function listDir(dir) {
2007
+ try {
2008
+ return fs8.readdirSync(dir);
2009
+ } catch {
2010
+ return [];
2011
+ }
2012
+ }
2013
+ function removeQuietly2(file) {
2014
+ try {
2015
+ fs8.rmSync(file, { force: true });
2016
+ } catch {
2017
+ }
2018
+ }
2019
+ function savedOutput(file) {
2020
+ const dir = path7.dirname(file);
2021
+ const stem = stemOf(path7.basename(file));
2022
+ const matches = listDir(dir).filter((n) => {
2023
+ if (PART_SUFFIX.test(n) || stemOf(n) !== stem) return false;
2024
+ const st = fs8.statSync(path7.join(dir, n), { throwIfNoEntry: false });
2025
+ return st?.isFile() === true && st.size > 0;
2026
+ });
2027
+ const exact = matches.find((n) => n === path7.basename(file));
2028
+ const found = exact ?? matches.sort()[0];
2029
+ return found === void 0 ? void 0 : path7.join(dir, found);
2030
+ }
2031
+ function* candidateNames(file) {
2032
+ const dir = path7.dirname(file);
2033
+ const ext = path7.extname(file);
2034
+ const stem = path7.basename(file, ext);
2035
+ const taken = new Set(listDir(dir).map(stemOf));
2036
+ for (let n = 0; n < MAX_UNIQUE_ATTEMPTS; n++) {
2037
+ const name = n === 0 ? stem : `${stem}-${n}`;
2038
+ if (!taken.has(fold(name))) yield path7.join(dir, `${name}${ext}`);
2039
+ }
2040
+ }
2041
+ function readLock(lock) {
2042
+ try {
2043
+ const v = JSON.parse(fs8.readFileSync(lock, "utf8"));
2044
+ if (Number.isInteger(v.pid) && v.pid > 0 && typeof v.host === "string") {
2045
+ return v;
2046
+ }
2047
+ } catch {
2048
+ }
2049
+ return void 0;
2050
+ }
2051
+ function isProcessAlive(pid) {
2052
+ if (pid === process.pid) return true;
2053
+ try {
2054
+ process.kill(pid, 0);
2055
+ return true;
2056
+ } catch (e) {
2057
+ return e.code !== "ESRCH";
2058
+ }
2059
+ }
2060
+ function isStaleLock(lock) {
2061
+ const info = readLock(lock);
2062
+ if (!info) {
2063
+ const st = fs8.statSync(lock, { throwIfNoEntry: false });
2064
+ return st === void 0 || Date.now() - st.mtimeMs > UNREADABLE_LOCK_GRACE_MS;
2065
+ }
2066
+ if (info.host !== os2.hostname()) return false;
2067
+ return !isProcessAlive(info.pid);
2068
+ }
2069
+ var activeClaims = /* @__PURE__ */ new Set();
2070
+ function tryLock(dest) {
2071
+ const lock = `${dest}${PART}`;
2072
+ const content = JSON.stringify({
2073
+ pid: process.pid,
2074
+ host: os2.hostname(),
2075
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2076
+ });
2077
+ for (let attempt = 0; attempt < 2; attempt++) {
2078
+ try {
2079
+ fs8.writeFileSync(lock, content, { flag: "wx" });
2080
+ activeClaims.add(dest);
2081
+ installInterruptCleanup();
2082
+ return true;
2083
+ } catch (e) {
2084
+ if (e.code !== "EEXIST") throw e;
2085
+ if (attempt > 0 || !isStaleLock(lock)) return false;
2086
+ removeQuietly2(lock);
2087
+ }
2088
+ }
2089
+ return false;
2090
+ }
2091
+ function removeOrphanedTemps(dest) {
2092
+ const base = path7.basename(dest);
2093
+ const dir = path7.dirname(dest);
2094
+ for (const n of listDir(dir)) {
2095
+ if (!n.startsWith(base)) continue;
2096
+ const m = TEMP_TAIL.exec(n.slice(base.length));
2097
+ if (m?.[1] && !isProcessAlive(Number(m[1]))) removeQuietly2(path7.join(dir, n));
2098
+ }
2099
+ }
2100
+ function releaseClaim(dest) {
2101
+ if (!activeClaims.delete(dest)) return;
2102
+ removeQuietly2(`${dest}${PART}`);
2103
+ }
2104
+ function cleanupActiveClaims() {
2105
+ for (const dest of [...activeClaims]) {
2106
+ const base = path7.basename(dest);
2107
+ const dir = path7.dirname(dest);
2108
+ for (const n of listDir(dir)) {
2109
+ if (!n.startsWith(base)) continue;
2110
+ const m = TEMP_TAIL.exec(n.slice(base.length));
2111
+ if (m?.[1] && Number(m[1]) === process.pid) removeQuietly2(path7.join(dir, n));
2112
+ }
2113
+ releaseClaim(dest);
2114
+ }
2115
+ }
2116
+ var INTERRUPT_EXIT_CODES = { SIGINT: 130, SIGTERM: 143 };
2117
+ function handleInterrupt(signal, exit = (code) => process.exit(code)) {
2118
+ cleanupActiveClaims();
2119
+ exit(INTERRUPT_EXIT_CODES[signal]);
2120
+ }
2121
+ var interruptCleanupInstalled = false;
2122
+ function installInterruptCleanup() {
2123
+ if (interruptCleanupInstalled) return;
2124
+ interruptCleanupInstalled = true;
2125
+ process.once("SIGINT", () => handleInterrupt("SIGINT"));
2126
+ process.once("SIGTERM", () => handleInterrupt("SIGTERM"));
2127
+ }
2128
+ function heldMessage(dest) {
2129
+ const info = readLock(`${dest}${PART}`);
2130
+ const holder = info ? ` (pid ${info.pid} on ${info.host})` : "";
2131
+ const advice = "Wait for it to finish, then retry. A lock left by a run that has stopped is replaced automatically.";
2132
+ return `${dest}${PART} is held by another multix run${holder} downloading this output. ${advice}`;
2133
+ }
2134
+ function claimOutputPath(file, policy) {
2135
+ if (policy === "overwrite") return file;
2136
+ fs8.mkdirSync(path7.dirname(file), { recursive: true });
2137
+ if (policy === "reuse") {
2138
+ if (!tryLock(file)) throw new ProviderError(heldMessage(file), ATLAS_PROVIDER);
2139
+ removeOrphanedTemps(file);
2140
+ return file;
2141
+ }
2142
+ for (const candidate of candidateNames(file)) {
2143
+ if (!tryLock(candidate)) continue;
2144
+ if (fs8.existsSync(candidate)) {
2145
+ releaseClaim(candidate);
2146
+ continue;
2147
+ }
2148
+ removeOrphanedTemps(candidate);
2149
+ return candidate;
2150
+ }
2151
+ throw new ProviderError(
2152
+ `No free file name for ${file} after ${MAX_UNIQUE_ATTEMPTS} attempts; choose another --output.`,
2153
+ ATLAS_PROVIDER
2154
+ );
2155
+ }
2156
+ async function downloadClaimed(url, choose, policy, opts) {
2157
+ let claimed;
2158
+ try {
2159
+ const { dest } = await downloadToFile(url, {
2160
+ ...opts,
2161
+ dest: (response) => {
2162
+ const dest2 = claimOutputPath(choose(response), policy);
2163
+ if (policy !== "overwrite") claimed = dest2;
2164
+ return dest2;
2165
+ }
2166
+ });
2167
+ return dest;
2168
+ } finally {
2169
+ if (claimed) releaseClaim(claimed);
2170
+ }
2171
+ }
2172
+
1825
2173
  // src/providers/atlascloud/commands/prediction-runner.ts
1826
2174
  function addRunOptions(cmd, cfg) {
1827
2175
  cmd.option("--async", "Submit and exit; print the prediction id (resume with `atlas status`)").option("--wait-timeout <ms>", "Max time to wait for completion", String(cfg.waitTimeoutMs)).option("--output <path>", "Output file (single output) or directory (path without extension)").option("--no-download", "Do not download media; print output URLs instead").option("--no-validate", "Skip client-side validation against the model's published schema");
@@ -1882,8 +2230,13 @@ function assertEndpoint(spec, expected, model) {
1882
2230
  function capitalize(s) {
1883
2231
  return s.charAt(0).toUpperCase() + s.slice(1);
1884
2232
  }
1885
- function resumeCommand(id) {
1886
- return `multix atlas status ${id} --wait --download`;
2233
+ function resumeCommand(id, output) {
2234
+ const base = `multix atlas status ${id} --wait --download`;
2235
+ return output ? `${base} --output ${shellQuote(output)}` : base;
2236
+ }
2237
+ function shellQuote(value) {
2238
+ if (/^[\w@%+=:,./\\-]+$/.test(value)) return value;
2239
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
1887
2240
  }
1888
2241
  async function executePrediction(o) {
1889
2242
  const { client, logger, opts } = o;
@@ -1893,22 +2246,27 @@ async function executePrediction(o) {
1893
2246
  const id = submitted.id;
1894
2247
  if (opts.async) {
1895
2248
  console.log(id);
1896
- console.error(`Prediction submitted. Resume with: ${resumeCommand(id)}`);
2249
+ console.error(`Prediction submitted. Resume with: ${resumeCommand(id, opts.output)}`);
1897
2250
  return { predictionId: id, files: [], texts: [] };
1898
2251
  }
1899
2252
  logger.info(`Prediction ${id} \u2014 waiting up to ${Math.round(timeoutMs / 1e3)}s...`);
1900
2253
  const final = isTerminalStatus(submitted.status) ? submitted : await client.waitForPrediction(id, {
1901
2254
  timeoutMs,
1902
2255
  intervalMs: o.pollIntervalMs,
1903
- resumeHint: `Resume with: ${resumeCommand(id)}`,
2256
+ resumeHint: `Resume with: ${resumeCommand(id, opts.output)}`,
1904
2257
  onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
1905
2258
  });
1906
2259
  assertPredictionSucceeded(final);
1907
- const delivered = await deliverPrediction(final, { logger, opts, hint: o.hint });
2260
+ const delivered = await deliverPrediction(final, {
2261
+ logger,
2262
+ opts,
2263
+ hint: o.hint,
2264
+ predictionId: id
2265
+ });
1908
2266
  return { predictionId: id, prediction: final, ...delivered };
1909
2267
  }
1910
2268
  function isDirectoryOutput(output) {
1911
- return /[\\/]$/.test(output) || path7.extname(output) === "" || fs8.existsSync(output) && fs8.statSync(output).isDirectory();
2269
+ return /[\\/]$/.test(output) || path8.extname(output) === "" || fs9.existsSync(output) && fs9.statSync(output).isDirectory();
1912
2270
  }
1913
2271
  function planOutputPaths(urls, cfg) {
1914
2272
  const exts = urls.map((u) => {
@@ -1917,32 +2275,34 @@ function planOutputPaths(urls, cfg) {
1917
2275
  });
1918
2276
  const output = cfg.output;
1919
2277
  if (!output || isDirectoryOutput(output)) {
1920
- const dir = output ? path7.resolve(output) : cfg.outDir();
1921
- return exts.map((ext, i) => path7.join(dir, outputFileName(cfg.predictionId, i, ext)));
2278
+ const dir = output ? path8.resolve(output) : cfg.outDir();
2279
+ return exts.map((ext, i) => path8.join(dir, outputFileName(cfg.predictionId, i, ext)));
1922
2280
  }
1923
- const abs = path7.resolve(output);
2281
+ const abs = path8.resolve(output);
1924
2282
  if (urls.length === 1) return [abs];
1925
- const base = abs.slice(0, -path7.extname(abs).length);
2283
+ const base = abs.slice(0, -path8.extname(abs).length);
1926
2284
  return exts.map((ext, i) => `${base}-${String(i + 1).padStart(2, "0")}.${ext}`);
1927
2285
  }
1928
2286
  async function deliverPrediction(prediction, cfg) {
1929
2287
  const { logger, opts } = cfg;
2288
+ const id = cfg.predictionId ?? prediction.id;
2289
+ const retry = resumeCommand(id, opts.output);
1930
2290
  if (prediction.price !== void 0 && prediction.price !== "") {
1931
2291
  logger.info(`Cost: $${prediction.price}`);
1932
2292
  }
1933
2293
  const classified = classifyOutputs(prediction);
1934
2294
  if (classified.mode === "empty") {
1935
2295
  throw new ProviderError(
1936
- hasNsfwFlag(prediction) ? `Prediction ${prediction.id} returned no output: blocked by content safety (has_nsfw_contents). Try a different prompt or input.` : `Prediction ${prediction.id} completed but returned no outputs.`,
2296
+ hasNsfwFlag(prediction) ? `Prediction ${id} returned no output: blocked by content safety (has_nsfw_contents). Try a different prompt or input.` : `Prediction ${id} completed but returned no outputs.`,
1937
2297
  ATLAS_PROVIDER
1938
2298
  );
1939
2299
  }
1940
2300
  for (const text of classified.texts) console.log(text);
1941
2301
  if (classified.mode === "text") {
1942
2302
  if (opts.output) {
1943
- const dest = isDirectoryOutput(opts.output) ? path7.resolve(opts.output, outputFileName(prediction.id, 0, "txt")) : path7.resolve(opts.output);
1944
- fs8.mkdirSync(path7.dirname(dest), { recursive: true });
1945
- fs8.writeFileSync(dest, `${classified.texts.join("\n\n")}
2303
+ const dest = isDirectoryOutput(opts.output) ? path8.resolve(opts.output, outputFileName(id, 0, "txt")) : path8.resolve(opts.output);
2304
+ fs9.mkdirSync(path8.dirname(dest), { recursive: true });
2305
+ fs9.writeFileSync(dest, `${classified.texts.join("\n\n")}
1946
2306
  `, "utf8");
1947
2307
  logger.success(`Saved text output to ${dest}`);
1948
2308
  return { files: [dest], texts: classified.texts };
@@ -1954,26 +2314,39 @@ async function deliverPrediction(prediction, cfg) {
1954
2314
  return { files: [], texts: classified.texts };
1955
2315
  }
1956
2316
  const dests = planOutputPaths(classified.urls, {
1957
- predictionId: prediction.id,
2317
+ predictionId: id,
1958
2318
  outDir: getOutputDir,
1959
2319
  output: opts.output,
1960
2320
  hint: cfg.hint
1961
2321
  });
1962
- const explicitFile = classified.urls.length === 1 && opts.output !== void 0 && !isDirectoryOutput(opts.output);
2322
+ const policy = namingPolicy(opts.output, classified.urls.length);
2323
+ const maxBytes = maxDownloadBytes();
1963
2324
  const saved = [];
2325
+ const downloaded = [];
2326
+ const reused = /* @__PURE__ */ new Set();
1964
2327
  let failed = 0;
1965
2328
  for (let i = 0; i < classified.urls.length; i++) {
1966
2329
  const url = classified.urls[i];
1967
2330
  const planned = dests[i];
1968
- const part = `${planned}.part`;
1969
2331
  try {
1970
- const contentType = await downloadFile(url, part);
1971
- const dest = explicitFile || outputExtension(url) ? planned : withContentTypeExtension(planned, contentType);
1972
- fs8.renameSync(part, dest);
2332
+ const existing = policy === "reuse" ? savedOutput(planned) : void 0;
2333
+ if (existing) {
2334
+ saved.push(existing);
2335
+ reused.add(existing);
2336
+ logger.info(`Already saved: ${existing}`);
2337
+ continue;
2338
+ }
2339
+ const dest = await downloadClaimed(
2340
+ url,
2341
+ (response) => policy === "overwrite" || outputExtension(url) ? planned : withContentTypeExtension(planned, response.headers.get("content-type")),
2342
+ policy,
2343
+ // Inline data: outputs are decoded by fetch, never sent over the network.
2344
+ { maxBytes, validateUrl: isDataUrl(url) ? void 0 : checkOutputUrl }
2345
+ );
1973
2346
  saved.push(dest);
2347
+ downloaded.push(dest);
1974
2348
  logger.debug(`Downloaded ${redactUrl(url)} -> ${dest}`);
1975
2349
  } catch (e) {
1976
- fs8.rmSync(part, { force: true });
1977
2350
  failed++;
1978
2351
  logger.warn(
1979
2352
  `Download failed (${redactUrl(url)}): ${e instanceof Error ? e.message : String(e)}`
@@ -1982,11 +2355,11 @@ async function deliverPrediction(prediction, cfg) {
1982
2355
  }
1983
2356
  if (saved.length === 0) {
1984
2357
  throw new ProviderError(
1985
- `Prediction ${prediction.id} produced ${classified.urls.length} output(s) but none downloaded. Retry with: ${resumeCommand(prediction.id)}`,
2358
+ `Prediction ${id} produced ${classified.urls.length} output(s) but none downloaded. Retry with: ${retry}`,
1986
2359
  ATLAS_PROVIDER
1987
2360
  );
1988
2361
  }
1989
- const images = saved.filter((f) => outputCategory(f) === "image");
2362
+ const images = downloaded.filter((f) => outputCategory(f) === "image");
1990
2363
  const fileOutput = opts.output && !isDirectoryOutput(opts.output) ? opts.output : void 0;
1991
2364
  const finalImages = finalizeGeneratedImages(images, {
1992
2365
  imageFormat: opts.imageFormat,
@@ -1997,23 +2370,42 @@ async function deliverPrediction(prediction, cfg) {
1997
2370
  const files = saved.map((f) => finalImages[images.indexOf(f)] ?? f);
1998
2371
  const firstVideo = files.find((f) => outputCategory(f) === "video");
1999
2372
  if (firstVideo) {
2000
- await maybeDownloadThumb(prediction, firstVideo, { skip: opts.thumb === false, logger });
2373
+ await maybeDownloadThumb(thumbnailSource(prediction), firstVideo, {
2374
+ skip: opts.thumb === false,
2375
+ logger,
2376
+ download: async (thumbUrl, planned) => {
2377
+ const existing = policy === "reuse" ? savedOutput(planned) : void 0;
2378
+ if (existing) return { dest: existing };
2379
+ const dest = await downloadClaimed(thumbUrl, () => planned, policy, {
2380
+ maxBytes,
2381
+ validateUrl: checkOutputUrl
2382
+ });
2383
+ return { dest };
2384
+ }
2385
+ });
2001
2386
  }
2002
2387
  console.log(`
2003
- Prediction ${prediction.id}: saved ${files.length} file(s)`);
2004
- for (const f of files) console.log(` ${f}`);
2388
+ Prediction ${id}: saved ${files.length} file(s)`);
2389
+ for (const f of files) console.log(` ${f}${reused.has(f) ? " (already saved)" : ""}`);
2005
2390
  if (failed > 0) {
2006
2391
  throw new ProviderError(
2007
- `Prediction ${prediction.id}: downloaded ${saved.length} of ${classified.urls.length} output(s); ${failed} failed. Saved: ${files.join(", ")}. Retry with: ${resumeCommand(prediction.id)}`,
2392
+ `Prediction ${id}: downloaded ${saved.length} of ${classified.urls.length} output(s); ${failed} failed. Saved: ${files.join(", ")}. Retry with: ${retry}`,
2008
2393
  ATLAS_PROVIDER
2009
2394
  );
2010
2395
  }
2011
2396
  return { files, texts: classified.texts };
2012
2397
  }
2398
+ function isDataUrl(url) {
2399
+ return /^data:/i.test(url.trim());
2400
+ }
2401
+ function namingPolicy(output, outputCount) {
2402
+ if (!output || isDirectoryOutput(output)) return "reuse";
2403
+ return outputCount === 1 ? "overwrite" : "unique";
2404
+ }
2013
2405
  function withContentTypeExtension(planned, contentType) {
2014
2406
  const ext = extensionForContentType(contentType);
2015
2407
  if (!ext) return planned;
2016
- const current = path7.extname(planned);
2408
+ const current = path8.extname(planned);
2017
2409
  return `${current ? planned.slice(0, -current.length) : planned}.${ext}`;
2018
2410
  }
2019
2411
 
@@ -2148,12 +2540,12 @@ init_errors();
2148
2540
 
2149
2541
  // src/core/json-input.ts
2150
2542
  init_errors();
2151
- import fs9 from "fs";
2543
+ import fs10 from "fs";
2152
2544
  function parseJsonInput(raw, flag = "--input") {
2153
2545
  let text;
2154
2546
  if (raw.startsWith("@")) {
2155
2547
  try {
2156
- text = fs9.readFileSync(raw.slice(1), "utf8");
2548
+ text = fs10.readFileSync(raw.slice(1), "utf8");
2157
2549
  } catch (e) {
2158
2550
  const msg = e instanceof Error ? e.message : String(e);
2159
2551
  throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
@@ -2238,7 +2630,7 @@ function registerAtlasStatusCommand(parent) {
2238
2630
  const wait = Boolean(opts.wait || opts.download);
2239
2631
  const prediction = wait ? await client.waitForPrediction(predictionId, {
2240
2632
  timeoutMs: parseTimeoutMs(opts.waitTimeout, STATUS_WAIT_MS),
2241
- resumeHint: `Resume with: ${resumeCommand(predictionId)}`,
2633
+ resumeHint: `Resume with: ${resumeCommand(predictionId, opts.output)}`,
2242
2634
  onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
2243
2635
  }) : await client.getPrediction(predictionId);
2244
2636
  if (!opts.download) {
@@ -2247,7 +2639,11 @@ function registerAtlasStatusCommand(parent) {
2247
2639
  return;
2248
2640
  }
2249
2641
  assertPredictionSucceeded(prediction);
2250
- await deliverPrediction(prediction, { logger, opts: { ...opts, download: true } });
2642
+ await deliverPrediction(prediction, {
2643
+ logger,
2644
+ opts: { ...opts, download: true },
2645
+ predictionId
2646
+ });
2251
2647
  });
2252
2648
  }
2253
2649
 
@@ -2377,11 +2773,11 @@ function registerAtlasCloudCommands(program2) {
2377
2773
  }
2378
2774
 
2379
2775
  // src/providers/byteplus/commands/generate-3d.ts
2380
- import path9 from "path";
2776
+ import path10 from "path";
2381
2777
 
2382
2778
  // src/core/image-input.ts
2383
- import fs10 from "fs/promises";
2384
- import path8 from "path";
2779
+ import fs11 from "fs/promises";
2780
+ import path9 from "path";
2385
2781
  var IMAGE_MIME_BY_EXT = {
2386
2782
  ".jpg": "image/jpeg",
2387
2783
  ".jpeg": "image/jpeg",
@@ -2408,7 +2804,7 @@ function isHttpUrl(input) {
2408
2804
  return /^https?:\/\//i.test(input);
2409
2805
  }
2410
2806
  async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
2411
- const ext = path8.extname(filePath).toLowerCase();
2807
+ const ext = path9.extname(filePath).toLowerCase();
2412
2808
  const mime = mimeTable[ext];
2413
2809
  if (!mime) {
2414
2810
  throw new Error(
@@ -2417,7 +2813,7 @@ async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
2417
2813
  }
2418
2814
  const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
2419
2815
  const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
2420
- const buf = await fs10.readFile(filePath);
2816
+ const buf = await fs11.readFile(filePath);
2421
2817
  if (buf.byteLength > hard) {
2422
2818
  throw new Error(
2423
2819
  `${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
@@ -2546,9 +2942,9 @@ var BytePlusClient = class {
2546
2942
  }
2547
2943
  apiKey;
2548
2944
  baseUrl;
2549
- async request(path57, opts = {}) {
2945
+ async request(path58, opts = {}) {
2550
2946
  const base = this.baseUrl.replace(/\/$/, "");
2551
- const url = new URL(base + path57);
2947
+ const url = new URL(base + path58);
2552
2948
  if (opts.query) {
2553
2949
  for (const [k, v] of Object.entries(opts.query)) {
2554
2950
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -2566,14 +2962,14 @@ var BytePlusClient = class {
2566
2962
  timeoutMs: opts.timeoutMs ?? 12e4
2567
2963
  });
2568
2964
  }
2569
- get(path57, query, logger) {
2570
- return this.request(path57, { method: "GET", query, logger });
2965
+ get(path58, query, logger) {
2966
+ return this.request(path58, { method: "GET", query, logger });
2571
2967
  }
2572
- post(path57, body, logger) {
2573
- return this.request(path57, { method: "POST", body, logger });
2968
+ post(path58, body, logger) {
2969
+ return this.request(path58, { method: "POST", body, logger });
2574
2970
  }
2575
- delete(path57, logger) {
2576
- return this.request(path57, { method: "DELETE", logger });
2971
+ delete(path58, logger) {
2972
+ return this.request(path58, { method: "DELETE", logger });
2577
2973
  }
2578
2974
  };
2579
2975
  function createBytePlusClient() {
@@ -2678,7 +3074,7 @@ function registerBytePlusGenerate3DCommand(parent) {
2678
3074
  process.exit(1);
2679
3075
  }
2680
3076
  const ext = inferModelFileExt(url);
2681
- const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
3077
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
2682
3078
  await downloadModelFile(url, outPath, logger);
2683
3079
  console.log(`
2684
3080
  Task: ${taskId}`);
@@ -2701,8 +3097,8 @@ Task: ${taskId}`);
2701
3097
  }
2702
3098
 
2703
3099
  // src/providers/byteplus/generators/image.ts
2704
- import fs11 from "fs";
2705
- import path10 from "path";
3100
+ import fs12 from "fs";
3101
+ import path11 from "path";
2706
3102
  var ASPECT_TO_SIZE = {
2707
3103
  "1:1": "2048x2048",
2708
3104
  "16:9": "2560x1440",
@@ -2766,14 +3162,14 @@ async function generateSeedream(client, opts) {
2766
3162
  }
2767
3163
  const ext = (() => {
2768
3164
  try {
2769
- const e = path10.extname(new URL(url).pathname);
3165
+ const e = path11.extname(new URL(url).pathname);
2770
3166
  return e || ".jpg";
2771
3167
  } catch {
2772
3168
  return ".jpg";
2773
3169
  }
2774
3170
  })();
2775
- const filename = opts.output && items.length === 1 ? path10.resolve(opts.output) : path10.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
2776
- fs11.mkdirSync(path10.dirname(filename), { recursive: true });
3171
+ const filename = opts.output && items.length === 1 ? path11.resolve(opts.output) : path11.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
3172
+ fs12.mkdirSync(path11.dirname(filename), { recursive: true });
2777
3173
  await downloadFile(url, filename);
2778
3174
  saved.push({ path: filename, url, seed: item.seed });
2779
3175
  logger?.success(`Saved ${filename}`);
@@ -2861,7 +3257,7 @@ function collect2(value, prev) {
2861
3257
  }
2862
3258
 
2863
3259
  // src/providers/byteplus/commands/image-to-video.ts
2864
- import path11 from "path";
3260
+ import path12 from "path";
2865
3261
 
2866
3262
  // src/providers/byteplus/generators/video.ts
2867
3263
  function buildFlagSuffix(opts) {
@@ -2984,7 +3380,7 @@ function registerBytePlusImageToVideoCommand(parent) {
2984
3380
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
2985
3381
  process.exit(1);
2986
3382
  }
2987
- const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3383
+ const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
2988
3384
  await downloadVideo(url, outPath, logger);
2989
3385
  await maybeDownloadThumb(final, outPath, {
2990
3386
  skip: opts.thumb === false,
@@ -3010,7 +3406,7 @@ Task: ${taskId}`);
3010
3406
  }
3011
3407
 
3012
3408
  // src/providers/byteplus/commands/reference-to-video.ts
3013
- import path12 from "path";
3409
+ import path13 from "path";
3014
3410
 
3015
3411
  // src/providers/byteplus/reference-input.ts
3016
3412
  var MAX_TOTAL_REFS = 12;
@@ -3042,10 +3438,10 @@ function toRef(kind, resolved, role) {
3042
3438
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
3043
3439
  }
3044
3440
  async function resolveRefSpec(kind, spec, logger) {
3045
- const { path: path57, role } = parseRefSpec(spec);
3046
- if (kind === "image") return toRef(kind, await resolveImageInput(path57, logger), role);
3047
- if (kind === "video") return toRef(kind, await resolveVideoInput(path57, logger), role);
3048
- return toRef(kind, await resolveAudioInput(path57, logger), role);
3441
+ const { path: path58, role } = parseRefSpec(spec);
3442
+ if (kind === "image") return toRef(kind, await resolveImageInput(path58, logger), role);
3443
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path58, logger), role);
3444
+ return toRef(kind, await resolveAudioInput(path58, logger), role);
3049
3445
  }
3050
3446
  async function resolveRefSpecs(specs, logger) {
3051
3447
  const out = [];
@@ -3142,7 +3538,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
3142
3538
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
3143
3539
  process.exit(1);
3144
3540
  }
3145
- const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3541
+ const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3146
3542
  await downloadVideo(url, outPath, logger);
3147
3543
  await maybeDownloadThumb(final, outPath, {
3148
3544
  skip: opts.thumb === false,
@@ -3168,7 +3564,7 @@ Task: ${taskId}`);
3168
3564
  }
3169
3565
 
3170
3566
  // src/providers/byteplus/commands/status.ts
3171
- import path13 from "path";
3567
+ import path14 from "path";
3172
3568
  function registerBytePlusStatusCommand(parent) {
3173
3569
  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(
3174
3570
  async (taskId, opts) => {
@@ -3186,7 +3582,7 @@ function registerBytePlusStatusCommand(parent) {
3186
3582
  const videoUrl2 = content?.video_url;
3187
3583
  const fileUrl = content?.file_url;
3188
3584
  if (videoUrl2) {
3189
- const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
3585
+ const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
3190
3586
  await downloadVideo(videoUrl2, outPath, logger);
3191
3587
  await maybeDownloadThumb(status, outPath, {
3192
3588
  skip: opts.thumb === false,
@@ -3196,7 +3592,7 @@ function registerBytePlusStatusCommand(parent) {
3196
3592
  console.log(`Saved: ${outPath}`);
3197
3593
  } else if (fileUrl) {
3198
3594
  const ext = inferModelFileExt(fileUrl);
3199
- const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
3595
+ const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
3200
3596
  await downloadModelFile(fileUrl, outPath, logger);
3201
3597
  console.log(`Saved: ${outPath}`);
3202
3598
  } else {
@@ -3220,7 +3616,7 @@ function registerBytePlusStatusCommand(parent) {
3220
3616
  }
3221
3617
 
3222
3618
  // src/providers/byteplus/commands/video.ts
3223
- import path14 from "path";
3619
+ import path15 from "path";
3224
3620
  function registerBytePlusVideoCommand(parent) {
3225
3621
  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(
3226
3622
  "--aspect-ratio <r>",
@@ -3262,7 +3658,7 @@ function registerBytePlusVideoCommand(parent) {
3262
3658
  logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
3263
3659
  process.exit(1);
3264
3660
  }
3265
- const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3661
+ const outPath = opts.output ?? path15.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3266
3662
  await downloadVideo(url, outPath, logger);
3267
3663
  await maybeDownloadThumb(final, outPath, {
3268
3664
  skip: opts.thumb === false,
@@ -3548,15 +3944,15 @@ async function downloadVideo2(url) {
3548
3944
 
3549
3945
  // src/providers/cloudflare/media-output.ts
3550
3946
  init_errors();
3551
- import fs12 from "fs";
3552
- import path15 from "path";
3947
+ import fs13 from "fs";
3948
+ import path16 from "path";
3553
3949
  function destination(filename, output) {
3554
- return output ? path15.resolve(output) : path15.join(getOutputDir(), filename);
3950
+ return output ? path16.resolve(output) : path16.join(getOutputDir(), filename);
3555
3951
  }
3556
3952
  function saveBytes(bytes, filename, output) {
3557
3953
  const dest = destination(filename, output);
3558
- fs12.mkdirSync(path15.dirname(dest), { recursive: true });
3559
- fs12.writeFileSync(dest, bytes);
3954
+ fs13.mkdirSync(path16.dirname(dest), { recursive: true });
3955
+ fs13.writeFileSync(dest, bytes);
3560
3956
  return dest;
3561
3957
  }
3562
3958
  function saveBase64Image(image, output) {
@@ -3739,8 +4135,8 @@ function registerCloudflareCommands(program2) {
3739
4135
  }
3740
4136
 
3741
4137
  // src/providers/elevenlabs/client.ts
3742
- import fs13 from "fs";
3743
- import path16 from "path";
4138
+ import fs14 from "fs";
4139
+ import path17 from "path";
3744
4140
  init_errors();
3745
4141
 
3746
4142
  // src/providers/elevenlabs/models.ts
@@ -3903,10 +4299,10 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
3903
4299
  return res.json();
3904
4300
  }
3905
4301
  function readFileAsBlob(filePath, fieldFilename) {
3906
- const abs = path16.resolve(filePath);
3907
- if (!fs13.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
3908
- const buf = fs13.readFileSync(abs);
3909
- const name = fieldFilename ?? path16.basename(abs);
4302
+ const abs = path17.resolve(filePath);
4303
+ if (!fs14.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4304
+ const buf = fs14.readFileSync(abs);
4305
+ const name = fieldFilename ?? path17.basename(abs);
3910
4306
  return new File([new Uint8Array(buf)], name);
3911
4307
  }
3912
4308
  async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
@@ -3930,12 +4326,12 @@ async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
3930
4326
  function saveBytes2(opts) {
3931
4327
  const { bytes, task, ext, outputCopy, logger } = opts;
3932
4328
  const dir = getOutputDir();
3933
- const dest = path16.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
3934
- fs13.writeFileSync(dest, bytes);
4329
+ const dest = path17.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
4330
+ fs14.writeFileSync(dest, bytes);
3935
4331
  logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
3936
4332
  if (outputCopy) {
3937
- fs13.mkdirSync(path16.dirname(path16.resolve(outputCopy)), { recursive: true });
3938
- fs13.copyFileSync(dest, outputCopy);
4333
+ fs14.mkdirSync(path17.dirname(path17.resolve(outputCopy)), { recursive: true });
4334
+ fs14.copyFileSync(dest, outputCopy);
3939
4335
  }
3940
4336
  return dest;
3941
4337
  }
@@ -3974,14 +4370,14 @@ ${models.length} models`);
3974
4370
  }
3975
4371
 
3976
4372
  // src/providers/elevenlabs/commands/align.ts
3977
- import fs14 from "fs";
3978
- import path17 from "path";
4373
+ import fs15 from "fs";
4374
+ import path18 from "path";
3979
4375
  function registerAlignCommand(parent) {
3980
4376
  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) => {
3981
4377
  const logger = createLogger({ verbose: opts.verbose ?? false });
3982
4378
  const apiKey = requireElevenLabsKey();
3983
4379
  let text = opts.text;
3984
- if (opts.textFile) text = fs14.readFileSync(path17.resolve(opts.textFile), "utf8");
4380
+ if (opts.textFile) text = fs15.readFileSync(path18.resolve(opts.textFile), "utf8");
3985
4381
  const form = new FormData();
3986
4382
  form.append("file", readFileAsBlob(opts.input));
3987
4383
  form.append("text", text);
@@ -3989,9 +4385,9 @@ function registerAlignCommand(parent) {
3989
4385
  const data = await apiPostMultipart("forced-alignment", form, apiKey, {
3990
4386
  timeoutMs: 6e5
3991
4387
  });
3992
- const dest = opts.output ? path17.resolve(opts.output) : path17.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
3993
- fs14.mkdirSync(path17.dirname(dest), { recursive: true });
3994
- fs14.writeFileSync(dest, JSON.stringify(data, null, 2));
4388
+ const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
4389
+ fs15.mkdirSync(path18.dirname(dest), { recursive: true });
4390
+ fs15.writeFileSync(dest, JSON.stringify(data, null, 2));
3995
4391
  logger.success(`Saved alignment: ${dest}`);
3996
4392
  if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
3997
4393
  });
@@ -4033,8 +4429,8 @@ function registerCloneCommand(parent) {
4033
4429
 
4034
4430
  // src/providers/elevenlabs/commands/dub.ts
4035
4431
  init_errors();
4036
- import fs15 from "fs";
4037
- import path18 from "path";
4432
+ import fs16 from "fs";
4433
+ import path19 from "path";
4038
4434
  function registerDubCommand(parent) {
4039
4435
  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) => {
4040
4436
  const logger = createLogger({ verbose: opts.verbose ?? false });
@@ -4077,7 +4473,7 @@ function registerDubCommand(parent) {
4077
4473
  );
4078
4474
  logger.success(`Dub complete: ${start.dubbing_id}`);
4079
4475
  if (opts.download) {
4080
- const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
4476
+ const dest = opts.output ? path19.resolve(opts.output) : path19.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
4081
4477
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
4082
4478
  await downloadDub(url, apiKey, dest);
4083
4479
  logger.success(`Saved: ${dest}`);
@@ -4092,7 +4488,7 @@ Dubbed file: ${dest}`);
4092
4488
  const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
4093
4489
  console.log(JSON.stringify(status, null, 2));
4094
4490
  if (opts.download) {
4095
- const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
4491
+ const dest = opts.output ? path19.resolve(opts.output) : path19.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
4096
4492
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
4097
4493
  await downloadDub(url, apiKey, dest);
4098
4494
  console.log(`Saved: ${dest}`);
@@ -4100,7 +4496,7 @@ Dubbed file: ${dest}`);
4100
4496
  });
4101
4497
  }
4102
4498
  async function downloadDub(url, apiKey, dest) {
4103
- fs15.mkdirSync(path18.dirname(dest), { recursive: true });
4499
+ fs16.mkdirSync(path19.dirname(dest), { recursive: true });
4104
4500
  const { response: res } = await fetchWithValidatedRedirects(url, {
4105
4501
  headers: { "xi-api-key": apiKey },
4106
4502
  signal: AbortSignal.timeout(6e5)
@@ -4109,7 +4505,7 @@ async function downloadDub(url, apiKey, dest) {
4109
4505
  throw new Error(`Failed to download dub: HTTP ${res.status}`);
4110
4506
  }
4111
4507
  const arr = new Uint8Array(await res.arrayBuffer());
4112
- fs15.writeFileSync(dest, arr);
4508
+ fs16.writeFileSync(dest, arr);
4113
4509
  }
4114
4510
 
4115
4511
  // src/providers/elevenlabs/commands/isolate.ts
@@ -4137,8 +4533,8 @@ Isolated audio: ${dest}`);
4137
4533
 
4138
4534
  // src/providers/elevenlabs/commands/music.ts
4139
4535
  init_errors();
4140
- import fs16 from "fs";
4141
- import path19 from "path";
4536
+ import fs17 from "fs";
4537
+ import path20 from "path";
4142
4538
  function registerMusicCommand(parent) {
4143
4539
  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(
4144
4540
  "--format <fmt>",
@@ -4153,9 +4549,9 @@ function registerMusicCommand(parent) {
4153
4549
  const payload = { model_id: opts.model };
4154
4550
  if (opts.prompt) payload.prompt = opts.prompt;
4155
4551
  if (opts.plan) {
4156
- const planPath = path19.resolve(opts.plan);
4157
- if (!fs16.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
4158
- const planJson = JSON.parse(fs16.readFileSync(planPath, "utf8"));
4552
+ const planPath = path20.resolve(opts.plan);
4553
+ if (!fs17.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
4554
+ const planJson = JSON.parse(fs17.readFileSync(planPath, "utf8"));
4159
4555
  payload.composition_plan = planJson;
4160
4556
  }
4161
4557
  if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
@@ -4214,8 +4610,8 @@ Generated SFX: ${dest}`);
4214
4610
  }
4215
4611
 
4216
4612
  // src/providers/elevenlabs/commands/transcribe.ts
4217
- import fs17 from "fs";
4218
- import path20 from "path";
4613
+ import fs18 from "fs";
4614
+ import path21 from "path";
4219
4615
  function registerTranscribeCommand(parent) {
4220
4616
  parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
4221
4617
  "--model <id>",
@@ -4238,7 +4634,7 @@ function registerTranscribeCommand(parent) {
4238
4634
  });
4239
4635
  const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
4240
4636
  const dir = getOutputDir();
4241
- const dest = opts.output ? path20.resolve(opts.output) : path20.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
4637
+ const dest = opts.output ? path21.resolve(opts.output) : path21.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
4242
4638
  let body;
4243
4639
  switch (opts.format) {
4244
4640
  case "json":
@@ -4253,8 +4649,8 @@ function registerTranscribeCommand(parent) {
4253
4649
  default:
4254
4650
  body = data.text ?? "";
4255
4651
  }
4256
- fs17.mkdirSync(path20.dirname(dest), { recursive: true });
4257
- fs17.writeFileSync(dest, body);
4652
+ fs18.mkdirSync(path21.dirname(dest), { recursive: true });
4653
+ fs18.writeFileSync(dest, body);
4258
4654
  logger.success(`Saved transcript: ${dest}`);
4259
4655
  if (data.language_code) {
4260
4656
  logger.info(
@@ -4500,8 +4896,8 @@ function registerElevenLabsCommands(program2) {
4500
4896
  }
4501
4897
 
4502
4898
  // src/providers/fal/commands/image.ts
4503
- import fs19 from "fs";
4504
- import path22 from "path";
4899
+ import fs20 from "fs";
4900
+ import path23 from "path";
4505
4901
 
4506
4902
  // src/providers/fal/models.ts
4507
4903
  var DEFAULT_FAL_BASE_URL = "https://queue.fal.run";
@@ -4520,14 +4916,43 @@ var FAL_DEFAULTS = {
4520
4916
  };
4521
4917
 
4522
4918
  // src/providers/fal/run-helpers.ts
4523
- import fs18 from "fs";
4524
- import path21 from "path";
4919
+ init_errors();
4920
+ import fs19 from "fs";
4921
+ import path22 from "path";
4525
4922
  init_redact();
4526
4923
 
4527
4924
  // src/providers/fal/client.ts
4528
4925
  init_errors();
4529
4926
  init_errors();
4530
4927
  init_redact();
4928
+ var FAL_QUEUE_HOST = "queue.fal.run";
4929
+ function parseUrl(url) {
4930
+ try {
4931
+ return new URL(url);
4932
+ } catch {
4933
+ return void 0;
4934
+ }
4935
+ }
4936
+ function normalizedHost(u) {
4937
+ return u.hostname.toLowerCase().replace(/\.$/, "");
4938
+ }
4939
+ function normalizedOrigin(u) {
4940
+ return `${u.protocol}//${normalizedHost(u)}:${u.port}`;
4941
+ }
4942
+ function assertTrustedFalUrl(url, baseUrl) {
4943
+ const target = parseUrl(url);
4944
+ if (!target) {
4945
+ throw new ProviderError("Refusing to send the fal key to a malformed URL", "fal");
4946
+ }
4947
+ const base = parseUrl(baseUrl);
4948
+ if (base && normalizedOrigin(target) === normalizedOrigin(base)) return;
4949
+ const host = normalizedHost(target);
4950
+ if (target.protocol === "https:" && host === FAL_QUEUE_HOST && target.port === "") return;
4951
+ throw new ProviderError(
4952
+ `Refusing to send the fal key to ${host || "this URL"}: not https://${FAL_QUEUE_HOST} or the FAL_BASE_URL origin`,
4953
+ "fal"
4954
+ );
4955
+ }
4531
4956
  function requireFalKey() {
4532
4957
  const key = resolveRoutedKey(FAL_ROUTING);
4533
4958
  if (!key) {
@@ -4546,13 +4971,25 @@ var FalClient = class {
4546
4971
  apiKey;
4547
4972
  baseUrl;
4548
4973
  /** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
4549
- async request(path57, opts = {}) {
4974
+ async request(path58, opts = {}) {
4550
4975
  const base = this.baseUrl.replace(/\/$/, "");
4551
- const suffix = path57.startsWith("/") ? path57 : `/${path57}`;
4552
- return this.requestAbsolute(base + suffix, opts);
4976
+ const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
4977
+ return this.send(base + suffix, opts);
4553
4978
  }
4554
- /** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
4979
+ /**
4980
+ * Same auth/headers as `request`, but `url` is used verbatim (e.g. a
4981
+ * status_url/response_url returned by submit). The URL must pass
4982
+ * {@link assertTrustedFalUrl} before the key is sent.
4983
+ */
4555
4984
  async requestAbsolute(url, opts = {}) {
4985
+ assertTrustedFalUrl(url, this.baseUrl);
4986
+ return this.send(url, opts);
4987
+ }
4988
+ /** Throws a ProviderError when `url` may not receive the key (see {@link assertTrustedFalUrl}). */
4989
+ assertTrustedUrl(url) {
4990
+ assertTrustedFalUrl(url, this.baseUrl);
4991
+ }
4992
+ async send(url, opts) {
4556
4993
  opts.logger?.debug(`${opts.method ?? "GET"} ${redactUrl(url)}`);
4557
4994
  return httpJson({
4558
4995
  url,
@@ -4565,11 +5002,11 @@ var FalClient = class {
4565
5002
  timeoutMs: opts.timeoutMs ?? 12e4
4566
5003
  });
4567
5004
  }
4568
- get(path57, logger) {
4569
- return this.request(path57, { method: "GET", logger });
5005
+ get(path58, logger) {
5006
+ return this.request(path58, { method: "GET", logger });
4570
5007
  }
4571
- post(path57, body, logger) {
4572
- return this.request(path57, { method: "POST", body, logger });
5008
+ post(path58, body, logger) {
5009
+ return this.request(path58, { method: "POST", body, logger });
4573
5010
  }
4574
5011
  getAbsolute(url, logger) {
4575
5012
  return this.requestAbsolute(url, { method: "GET", logger });
@@ -4620,6 +5057,13 @@ async function submitAndWait(opts) {
4620
5057
  const submitRes = await client.post(submitPath(model), input, logger);
4621
5058
  const requestId = submitRes.request_id;
4622
5059
  logger.debug(`request_id: ${requestId}`);
5060
+ for (const field of ["status_url", "response_url"]) {
5061
+ const url = submitRes[field];
5062
+ if (typeof url !== "string" || url === "") {
5063
+ throw new ProviderError(`fal submit response is missing ${field}`, "fal");
5064
+ }
5065
+ client.assertTrustedUrl(url);
5066
+ }
4623
5067
  logger.info("Polling for completion...");
4624
5068
  const waitTimeoutMs = opts.waitTimeoutMs ?? 48e4;
4625
5069
  const intervalMs = opts.pollIntervalMs ?? 3e3;
@@ -4711,7 +5155,7 @@ function extractMediaUrls(result) {
4711
5155
  function extFromUrl(url, fallback = ".bin") {
4712
5156
  try {
4713
5157
  const u = new URL(url);
4714
- const e = path21.extname(u.pathname);
5158
+ const e = path22.extname(u.pathname);
4715
5159
  return e.length > 0 ? e : fallback;
4716
5160
  } catch {
4717
5161
  return fallback;
@@ -4724,13 +5168,13 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
4724
5168
  if (!url) continue;
4725
5169
  const ext = extFromUrl(url);
4726
5170
  const filename = `fal-${requestId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
4727
- const filepath = path21.join(outDir, filename);
5171
+ const filepath = path22.join(outDir, filename);
4728
5172
  try {
4729
5173
  await downloadFile(url, filepath);
4730
5174
  saved.push(filepath);
4731
5175
  logger.success(`Saved ${filepath}`);
4732
5176
  } catch (e) {
4733
- fs18.rmSync(filepath, { force: true });
5177
+ fs19.rmSync(filepath, { force: true });
4734
5178
  logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
4735
5179
  }
4736
5180
  }
@@ -4775,9 +5219,9 @@ function registerFalImageCommand(parent) {
4775
5219
  process.exit(1);
4776
5220
  }
4777
5221
  if (opts.output && saved[0]) {
4778
- const dest = path22.resolve(opts.output);
4779
- fs19.mkdirSync(path22.dirname(dest), { recursive: true });
4780
- fs19.copyFileSync(saved[0], dest);
5222
+ const dest = path23.resolve(opts.output);
5223
+ fs20.mkdirSync(path23.dirname(dest), { recursive: true });
5224
+ fs20.copyFileSync(saved[0], dest);
4781
5225
  logger.success(`Copied to ${dest}`);
4782
5226
  }
4783
5227
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4932,9 +5376,9 @@ function registerFalCommands(program2) {
4932
5376
 
4933
5377
  // src/core/stdin-file.ts
4934
5378
  init_errors();
4935
- import fs20 from "fs";
4936
- import os2 from "os";
4937
- import path23 from "path";
5379
+ import fs21 from "fs";
5380
+ import os3 from "os";
5381
+ import path24 from "path";
4938
5382
 
4939
5383
  // src/core/limits.ts
4940
5384
  var FILES_API_MAX_BYTES = 2e9;
@@ -5039,7 +5483,7 @@ function trackTempDir(dir) {
5039
5483
  let done = false;
5040
5484
  const remove = () => {
5041
5485
  try {
5042
- fs20.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
5486
+ fs21.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
5043
5487
  } catch {
5044
5488
  }
5045
5489
  };
@@ -5071,9 +5515,9 @@ async function stdinToTempFile(opts = {}) {
5071
5515
  }
5072
5516
  const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
5073
5517
  const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
5074
- const dir = fs20.mkdtempSync(path23.join(os2.tmpdir(), "multix-stdin-"));
5518
+ const dir = fs21.mkdtempSync(path24.join(os3.tmpdir(), "multix-stdin-"));
5075
5519
  const cleanup = trackTempDir(dir);
5076
- const rawPath = path23.join(dir, "input");
5520
+ const rawPath = path24.join(dir, "input");
5077
5521
  const hint = setTimeout(() => {
5078
5522
  process.stderr.write(`Waiting for data on stdin (\`${flag} -\`)... press Ctrl+C to cancel
5079
5523
  `);
@@ -5082,7 +5526,7 @@ async function stdinToTempFile(opts = {}) {
5082
5526
  let head = Buffer.alloc(0);
5083
5527
  let size = 0;
5084
5528
  try {
5085
- const fd = fs20.openSync(rawPath, "w", 384);
5529
+ const fd = fs21.openSync(rawPath, "w", 384);
5086
5530
  try {
5087
5531
  for await (const chunk of input) {
5088
5532
  clearTimeout(hint);
@@ -5094,17 +5538,17 @@ async function stdinToTempFile(opts = {}) {
5094
5538
  head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
5095
5539
  }
5096
5540
  let offset = 0;
5097
- while (offset < buf.length) offset += fs20.writeSync(fd, buf, offset);
5541
+ while (offset < buf.length) offset += fs21.writeSync(fd, buf, offset);
5098
5542
  size += buf.length;
5099
5543
  }
5100
5544
  } finally {
5101
5545
  clearTimeout(hint);
5102
- fs20.closeSync(fd);
5546
+ fs21.closeSync(fd);
5103
5547
  }
5104
5548
  if (size === 0) throw new ValidationError(`\`${flag} -\` was given but stdin was empty`);
5105
5549
  const detectedExt = forcedExt ?? detectExtension(head);
5106
- const finalPath = path23.join(dir, `stdin${detectedExt ?? ".bin"}`);
5107
- fs20.renameSync(rawPath, finalPath);
5550
+ const finalPath = path24.join(dir, `stdin${detectedExt ?? ".bin"}`);
5551
+ fs21.renameSync(rawPath, finalPath);
5108
5552
  return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
5109
5553
  } catch (error) {
5110
5554
  cleanup();
@@ -5122,7 +5566,7 @@ async function resolveFileInputs(files, opts = {}) {
5122
5566
  throw new ValidationError(`--stdin-ext only applies when ${flag} includes \`-\``);
5123
5567
  }
5124
5568
  for (const f of files) {
5125
- if (f !== STDIN_ARG && !fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
5569
+ if (f !== STDIN_ARG && !fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
5126
5570
  }
5127
5571
  if (stdinCount === 0) {
5128
5572
  return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
@@ -5410,12 +5854,12 @@ async function withGeminiKey(fn, opts = {}) {
5410
5854
  }
5411
5855
 
5412
5856
  // src/providers/gemini/local-files.ts
5413
- import fs21 from "fs";
5414
- import path25 from "path";
5857
+ import fs22 from "fs";
5858
+ import path26 from "path";
5415
5859
  init_errors();
5416
5860
 
5417
5861
  // src/providers/gemini/task-resolver.ts
5418
- import path24 from "path";
5862
+ import path25 from "path";
5419
5863
  var MIME_MAP = {
5420
5864
  // Audio
5421
5865
  ".mp3": "audio/mp3",
@@ -5456,7 +5900,7 @@ var MIME_MAP = {
5456
5900
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
5457
5901
  };
5458
5902
  function getMimeType(filePath) {
5459
- const ext = path24.extname(filePath).toLowerCase();
5903
+ const ext = path25.extname(filePath).toLowerCase();
5460
5904
  return MIME_MAP[ext] ?? "application/octet-stream";
5461
5905
  }
5462
5906
 
@@ -5476,10 +5920,10 @@ function minUploadBytesPerSec() {
5476
5920
  }
5477
5921
  async function uploadLocalFile(filePath, opts = {}) {
5478
5922
  const bytesPerSec = minUploadBytesPerSec();
5479
- const blob = await fs21.openAsBlob(filePath);
5923
+ const blob = await fs22.openAsBlob(filePath);
5480
5924
  const source = {
5481
5925
  blob,
5482
- displayName: path25.basename(filePath),
5926
+ displayName: path26.basename(filePath),
5483
5927
  mimeType: getMimeType(filePath),
5484
5928
  label: filePath
5485
5929
  };
@@ -5487,12 +5931,12 @@ async function uploadLocalFile(filePath, opts = {}) {
5487
5931
  return opts.apiKey ? upload(opts.apiKey) : withGeminiKey(upload, { logger: opts.logger });
5488
5932
  }
5489
5933
  function inlinePart(filePath) {
5490
- const data = fs21.readFileSync(filePath).toString("base64");
5934
+ const data = fs22.readFileSync(filePath).toString("base64");
5491
5935
  const mimeType = getMimeType(filePath);
5492
5936
  return { inlineData: { mimeType, data } };
5493
5937
  }
5494
5938
  function shouldUseFileApi(filePath) {
5495
- const stat = fs21.statSync(filePath);
5939
+ const stat = fs22.statSync(filePath);
5496
5940
  return stat.size > INLINE_MAX_BYTES;
5497
5941
  }
5498
5942
 
@@ -5543,13 +5987,13 @@ function getDefaultModel(task) {
5543
5987
  }
5544
5988
 
5545
5989
  // src/providers/gemini/result-formatter.ts
5546
- import fs22 from "fs";
5547
- import path26 from "path";
5990
+ import fs23 from "fs";
5991
+ import path27 from "path";
5548
5992
  function formatResults(results, format, outputPath) {
5549
5993
  const content = renderFormat(results, format);
5550
5994
  if (outputPath) {
5551
- fs22.mkdirSync(path26.dirname(path26.resolve(outputPath)), { recursive: true });
5552
- fs22.writeFileSync(outputPath, content, "utf8");
5995
+ fs23.mkdirSync(path27.dirname(path27.resolve(outputPath)), { recursive: true });
5996
+ fs23.writeFileSync(outputPath, content, "utf8");
5553
5997
  } else {
5554
5998
  process.stdout.write(content);
5555
5999
  }
@@ -5733,8 +6177,8 @@ function registerExtractCommand(parent) {
5733
6177
  init_errors();
5734
6178
 
5735
6179
  // src/providers/gemini/generators/speech.ts
5736
- import fs23 from "fs";
5737
- import path27 from "path";
6180
+ import fs24 from "fs";
6181
+ import path28 from "path";
5738
6182
 
5739
6183
  // src/providers/gemini/voices.ts
5740
6184
  var GEMINI_TTS_VOICES = [
@@ -5906,12 +6350,12 @@ function extractWavData(wav) {
5906
6350
  }
5907
6351
  function saveSpeech(outBytes, outputFormat, output, logger) {
5908
6352
  const outDir = getOutputDir();
5909
- const dest = path27.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
5910
- fs23.writeFileSync(dest, outBytes);
6353
+ const dest = path28.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
6354
+ fs24.writeFileSync(dest, outBytes);
5911
6355
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
5912
6356
  if (output) {
5913
- fs23.mkdirSync(path27.dirname(path27.resolve(output)), { recursive: true });
5914
- fs23.copyFileSync(dest, output);
6357
+ fs24.mkdirSync(path28.dirname(path28.resolve(output)), { recursive: true });
6358
+ fs24.copyFileSync(dest, output);
5915
6359
  logger?.success(`Copied to: ${output}`);
5916
6360
  }
5917
6361
  return dest;
@@ -6024,24 +6468,24 @@ function collectSpeakers(value, prev) {
6024
6468
 
6025
6469
  // src/providers/gemini/commands/generate-video.ts
6026
6470
  init_errors();
6027
- import fs25 from "fs";
6028
- import path28 from "path";
6471
+ import fs26 from "fs";
6472
+ import path29 from "path";
6029
6473
 
6030
6474
  // src/providers/gemini/veo.ts
6031
- import fs24 from "fs";
6475
+ import fs25 from "fs";
6032
6476
  init_errors();
6033
6477
  var API_HOST = "generativelanguage.googleapis.com";
6034
6478
  var BASE2 = `https://${API_HOST}/v1beta`;
6035
6479
  var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
6036
6480
  var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
6037
6481
  function veoImageFromFile(imgPath) {
6038
- if (!fs24.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
6482
+ if (!fs25.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
6039
6483
  const detected = getMimeType(imgPath);
6040
6484
  const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
6041
6485
  if (!VEO_IMAGE_TYPES.has(mimeType)) {
6042
6486
  throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
6043
6487
  }
6044
- return { bytesBase64Encoded: fs24.readFileSync(imgPath).toString("base64"), mimeType };
6488
+ return { bytesBase64Encoded: fs25.readFileSync(imgPath).toString("base64"), mimeType };
6045
6489
  }
6046
6490
  function veoFramesFromPaths(paths = []) {
6047
6491
  if (paths.length > 2) {
@@ -6167,7 +6611,7 @@ function registerGenerateVideoCommand(parent) {
6167
6611
  logger.debug(`Submitting video generation with ${model}...`);
6168
6612
  logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
6169
6613
  const frames = veoFramesFromPaths(opts.referenceImages);
6170
- const dest = path28.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
6614
+ const dest = path29.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
6171
6615
  const finalOp = await runVeo(
6172
6616
  model,
6173
6617
  {
@@ -6181,8 +6625,8 @@ function registerGenerateVideoCommand(parent) {
6181
6625
  );
6182
6626
  logger.success(`Saved video: ${dest}`);
6183
6627
  if (opts.output) {
6184
- fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
6185
- fs25.copyFileSync(dest, opts.output);
6628
+ fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
6629
+ fs26.copyFileSync(dest, opts.output);
6186
6630
  logger.success(`Copied to: ${opts.output}`);
6187
6631
  }
6188
6632
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -6198,8 +6642,8 @@ Generated video: ${dest}`);
6198
6642
 
6199
6643
  // src/providers/gemini/commands/generate.ts
6200
6644
  init_errors();
6201
- import fs26 from "fs";
6202
- import path29 from "path";
6645
+ import fs27 from "fs";
6646
+ import path30 from "path";
6203
6647
  function registerGenerateCommand(parent) {
6204
6648
  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(
6205
6649
  async (opts) => {
@@ -6247,8 +6691,8 @@ function registerGenerateCommand(parent) {
6247
6691
  const outDir = getOutputDir();
6248
6692
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
6249
6693
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
6250
- const dest = path29.join(outDir, fname);
6251
- fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
6694
+ const dest = path30.join(outDir, fname);
6695
+ fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
6252
6696
  savedFiles.push(dest);
6253
6697
  logger.success(`Saved: ${dest}`);
6254
6698
  }
@@ -6263,8 +6707,8 @@ function registerGenerateCommand(parent) {
6263
6707
  process.exit(1);
6264
6708
  }
6265
6709
  if (opts.output && savedFiles[0]) {
6266
- fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
6267
- fs26.copyFileSync(savedFiles[0], opts.output);
6710
+ fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
6711
+ fs27.copyFileSync(savedFiles[0], opts.output);
6268
6712
  logger.success(`Copied to: ${opts.output}`);
6269
6713
  }
6270
6714
  const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
@@ -6276,8 +6720,8 @@ Generated ${finalImages.length} image(s):`);
6276
6720
  }
6277
6721
 
6278
6722
  // src/providers/gemini/commands/image-to-image.ts
6279
- import fs27 from "fs";
6280
- import path30 from "path";
6723
+ import fs28 from "fs";
6724
+ import path31 from "path";
6281
6725
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
6282
6726
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
6283
6727
  function registerGeminiImageToImageCommand(parent) {
@@ -6340,14 +6784,14 @@ function registerGeminiImageToImageCommand(parent) {
6340
6784
  const img = images[i];
6341
6785
  if (!img) continue;
6342
6786
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
6343
- const dest = path30.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
6344
- fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
6787
+ const dest = path31.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
6788
+ fs28.writeFileSync(dest, Buffer.from(img.data, "base64"));
6345
6789
  saved.push(dest);
6346
6790
  logger.success(`Saved: ${dest}`);
6347
6791
  }
6348
6792
  if (opts.output && saved[0]) {
6349
- fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
6350
- fs27.copyFileSync(saved[0], opts.output);
6793
+ fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
6794
+ fs28.copyFileSync(saved[0], opts.output);
6351
6795
  logger.success(`Copied to: ${opts.output}`);
6352
6796
  }
6353
6797
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -6375,7 +6819,7 @@ function sniffImageMime(buf, url) {
6375
6819
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
6376
6820
  return "image/webp";
6377
6821
  }
6378
- const ext = path30.extname(new URL(url).pathname).toLowerCase();
6822
+ const ext = path31.extname(new URL(url).pathname).toLowerCase();
6379
6823
  if (ext === ".png") return "image/png";
6380
6824
  if (ext === ".webp") return "image/webp";
6381
6825
  if (ext === ".gif") return "image/gif";
@@ -6384,8 +6828,8 @@ function sniffImageMime(buf, url) {
6384
6828
 
6385
6829
  // src/providers/gemini/commands/image-to-video.ts
6386
6830
  init_errors();
6387
- import fs28 from "fs";
6388
- import path31 from "path";
6831
+ import fs29 from "fs";
6832
+ import path32 from "path";
6389
6833
  function registerGeminiImageToVideoCommand(parent) {
6390
6834
  parent.command("image-to-video <imagePath>").alias("i2v").description("Generate video from a starting image with Veo (image-to-video) [EXPERIMENTAL]").requiredOption("--prompt <text>", "Motion/scene prompt").option("--last-frame <path>", "Optional closing frame image (Veo 3.1)").option("--model <id>", "Veo model id").option("--resolution <res>", "720p|1080p", "1080p").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option("--output <path>", "Copy generated video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
6391
6835
  async (imagePath, opts) => {
@@ -6405,7 +6849,7 @@ function registerGeminiImageToVideoCommand(parent) {
6405
6849
  logger.info(
6406
6850
  "Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
6407
6851
  );
6408
- const dest = path31.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
6852
+ const dest = path32.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
6409
6853
  const finalOp = await runVeo(
6410
6854
  model,
6411
6855
  {
@@ -6420,8 +6864,8 @@ function registerGeminiImageToVideoCommand(parent) {
6420
6864
  );
6421
6865
  logger.success(`Saved video: ${dest}`);
6422
6866
  if (opts.output) {
6423
- fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
6424
- fs28.copyFileSync(dest, opts.output);
6867
+ fs29.mkdirSync(path32.dirname(path32.resolve(opts.output)), { recursive: true });
6868
+ fs29.copyFileSync(dest, opts.output);
6425
6869
  logger.success(`Copied to: ${opts.output}`);
6426
6870
  }
6427
6871
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -6626,8 +7070,8 @@ var HEYGEN_OPERATIONS = {
6626
7070
  };
6627
7071
 
6628
7072
  // src/providers/heygen/node-helpers.ts
6629
- import fs29 from "fs";
6630
- import path32 from "path";
7073
+ import fs30 from "fs";
7074
+ import path33 from "path";
6631
7075
  init_errors();
6632
7076
  init_redact();
6633
7077
 
@@ -6740,12 +7184,12 @@ function describePayload(data) {
6740
7184
  );
6741
7185
  return (text ?? String(data)).slice(0, 500);
6742
7186
  }
6743
- function requireJobId(data, field, path57) {
7187
+ function requireJobId(data, field, path58) {
6744
7188
  const value = data?.[field];
6745
7189
  const ok2 = Array.isArray(value) ? value.length > 0 && value.every((v) => typeof v === "string" && v.length > 0) : typeof value === "string" && value.length > 0;
6746
7190
  if (!ok2) {
6747
7191
  throw new ProviderError(
6748
- `HeyGen ${path57} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
7192
+ `HeyGen ${path58} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
6749
7193
  "heygen"
6750
7194
  );
6751
7195
  }
@@ -6770,8 +7214,8 @@ var HeygenClient = class {
6770
7214
  this.newIdempotencyKey = opts.newIdempotencyKey ?? (() => crypto.randomUUID());
6771
7215
  }
6772
7216
  /** Build an absolute URL for a v3 path plus query parameters. */
6773
- url(path57, query) {
6774
- const suffix = path57.startsWith("/") ? path57 : `/${path57}`;
7217
+ url(path58, query) {
7218
+ const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
6775
7219
  const params = new URLSearchParams();
6776
7220
  for (const [k, v] of Object.entries(query ?? {})) {
6777
7221
  if (v !== void 0 && v !== "") params.set(k, String(v));
@@ -6793,10 +7237,10 @@ var HeygenClient = class {
6793
7237
  * `request_in_progress` are retried only when an Idempotency-Key makes the
6794
7238
  * replay safe. GET requests are left to the caller's polling loop.
6795
7239
  */
6796
- async send(path57, opts = {}) {
7240
+ async send(path58, opts = {}) {
6797
7241
  const method = opts.method ?? "GET";
6798
7242
  const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : opts.idempotencyKey ? this.newIdempotencyKey() : void 0;
6799
- const url = this.url(path57, opts.query);
7243
+ const url = this.url(path58, opts.query);
6800
7244
  for (let attempt = 1; ; attempt++) {
6801
7245
  try {
6802
7246
  return await fetchJson({
@@ -6830,18 +7274,18 @@ var HeygenClient = class {
6830
7274
  return void 0;
6831
7275
  }
6832
7276
  /** Send and unwrap `{ data }`. A 2xx body carrying `error` is treated as a failure. */
6833
- async request(path57, opts = {}) {
6834
- const json = await this.send(path57, opts);
6835
- return unwrapData(json, path57);
7277
+ async request(path58, opts = {}) {
7278
+ const json = await this.send(path58, opts);
7279
+ return unwrapData(json, path58);
6836
7280
  }
6837
- async page(path57, query) {
6838
- const json = await this.send(path57, {
7281
+ async page(path58, query) {
7282
+ const json = await this.send(path58, {
6839
7283
  query
6840
7284
  });
6841
- const items = unwrapData(json, path57);
7285
+ const items = unwrapData(json, path58);
6842
7286
  if (!Array.isArray(items)) {
6843
7287
  throw new ProviderError(
6844
- `Unexpected HeyGen response from ${path57}: data is not a list`,
7288
+ `Unexpected HeyGen response from ${path58}: data is not a list`,
6845
7289
  "heygen"
6846
7290
  );
6847
7291
  }
@@ -6859,9 +7303,9 @@ var HeygenClient = class {
6859
7303
  // ── Video Agent ───────────────────────────────────────────────────────
6860
7304
  /** POST /v3/video-agents. No Idempotency-Key: the endpoint does not document one. */
6861
7305
  async createVideoAgent(body) {
6862
- const path57 = "/v3/video-agents";
6863
- const data = await this.request(path57, { method: "POST", body });
6864
- return requireJobId(data, "session_id", path57);
7306
+ const path58 = "/v3/video-agents";
7307
+ const data = await this.request(path58, { method: "POST", body });
7308
+ return requireJobId(data, "session_id", path58);
6865
7309
  }
6866
7310
  async getVideoAgentSession(sessionId, opts = {}) {
6867
7311
  return this.request(`/v3/video-agents/${seg(sessionId, "session id")}`, opts);
@@ -6871,36 +7315,36 @@ var HeygenClient = class {
6871
7315
  }
6872
7316
  // ── Videos ────────────────────────────────────────────────────────────
6873
7317
  async createVideo(body, idempotencyKey = true) {
6874
- const path57 = "/v3/videos";
6875
- const data = await this.request(path57, {
7318
+ const path58 = "/v3/videos";
7319
+ const data = await this.request(path58, {
6876
7320
  method: "POST",
6877
7321
  body,
6878
7322
  idempotencyKey
6879
7323
  });
6880
- return requireJobId(data, "video_id", path57);
7324
+ return requireJobId(data, "video_id", path58);
6881
7325
  }
6882
7326
  async getVideo(videoId, opts = {}) {
6883
7327
  return this.request(`/v3/videos/${seg(videoId, "video id")}`, opts);
6884
7328
  }
6885
7329
  /** POST /v3/templates/{id}. Returns the created video's `VideoDetail`. */
6886
7330
  async generateFromTemplate(templateId, body, idempotencyKey = true) {
6887
- const path57 = `/v3/templates/${seg(templateId, "template id")}`;
6888
- const data = await this.request(path57, {
7331
+ const path58 = `/v3/templates/${seg(templateId, "template id")}`;
7332
+ const data = await this.request(path58, {
6889
7333
  method: "POST",
6890
7334
  body,
6891
7335
  idempotencyKey
6892
7336
  });
6893
- return requireJobId(data, "id", path57);
7337
+ return requireJobId(data, "id", path58);
6894
7338
  }
6895
7339
  // ── HeyGen Video model ────────────────────────────────────────────────
6896
7340
  async createModelVideo(body, idempotencyKey = true) {
6897
- const path57 = "/v3/models/videos";
6898
- const data = await this.request(path57, {
7341
+ const path58 = "/v3/models/videos";
7342
+ const data = await this.request(path58, {
6899
7343
  method: "POST",
6900
7344
  body,
6901
7345
  idempotencyKey
6902
7346
  });
6903
- return requireJobId(data, "video_id", path57);
7347
+ return requireJobId(data, "video_id", path58);
6904
7348
  }
6905
7349
  async getModelVideo(videoId, opts = {}) {
6906
7350
  return this.request(`/v3/models/videos/${seg(videoId, "video id")}`, opts);
@@ -6922,13 +7366,13 @@ var HeygenClient = class {
6922
7366
  }
6923
7367
  // ── Translation & lipsync ─────────────────────────────────────────────
6924
7368
  async createVideoTranslation(body, idempotencyKey = true) {
6925
- const path57 = "/v3/video-translations";
6926
- const data = await this.request(path57, {
7369
+ const path58 = "/v3/video-translations";
7370
+ const data = await this.request(path58, {
6927
7371
  method: "POST",
6928
7372
  body,
6929
7373
  idempotencyKey
6930
7374
  });
6931
- return requireJobId(data, "video_translation_ids", path57);
7375
+ return requireJobId(data, "video_translation_ids", path58);
6932
7376
  }
6933
7377
  async getVideoTranslation(id, opts = {}) {
6934
7378
  return this.request(`/v3/video-translations/${seg(id, "video translation id")}`, opts);
@@ -6941,13 +7385,13 @@ var HeygenClient = class {
6941
7385
  return data.languages.filter((l) => typeof l === "string");
6942
7386
  }
6943
7387
  async createLipsync(body, idempotencyKey = true) {
6944
- const path57 = "/v3/lipsyncs";
6945
- const data = await this.request(path57, {
7388
+ const path58 = "/v3/lipsyncs";
7389
+ const data = await this.request(path58, {
6946
7390
  method: "POST",
6947
7391
  body,
6948
7392
  idempotencyKey
6949
7393
  });
6950
- return requireJobId(data, "lipsync_id", path57);
7394
+ return requireJobId(data, "lipsync_id", path58);
6951
7395
  }
6952
7396
  async getLipsync(id, opts = {}) {
6953
7397
  return this.request(`/v3/lipsyncs/${seg(id, "lipsync id")}`, opts);
@@ -7012,9 +7456,9 @@ var HeygenClient = class {
7012
7456
  }
7013
7457
  }
7014
7458
  };
7015
- function unwrapData(json, path57) {
7459
+ function unwrapData(json, path58) {
7016
7460
  if (json === null || typeof json !== "object") {
7017
- throw new ProviderError(`Unexpected HeyGen response from ${path57}: not a JSON object`, "heygen");
7461
+ throw new ProviderError(`Unexpected HeyGen response from ${path58}: not a JSON object`, "heygen");
7018
7462
  }
7019
7463
  const record = json;
7020
7464
  const error = record.error;
@@ -7026,7 +7470,7 @@ function unwrapData(json, path57) {
7026
7470
  );
7027
7471
  }
7028
7472
  if (!("data" in record) || record.data === null || record.data === void 0) {
7029
- throw new ProviderError(`Unexpected HeyGen response from ${path57}: missing data`, "heygen");
7473
+ throw new ProviderError(`Unexpected HeyGen response from ${path58}: missing data`, "heygen");
7030
7474
  }
7031
7475
  return record.data;
7032
7476
  }
@@ -7239,7 +7683,6 @@ function resumeCommand2(kind, id, opts = {}) {
7239
7683
 
7240
7684
  // src/providers/heygen/request-builders.ts
7241
7685
  init_errors();
7242
- init_redact();
7243
7686
  function oneOf(value, allowed, flag) {
7244
7687
  if (value === void 0) return void 0;
7245
7688
  if (!allowed.includes(value)) {
@@ -7268,23 +7711,6 @@ function isHttpsUrl(url) {
7268
7711
  return false;
7269
7712
  }
7270
7713
  }
7271
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
7272
- function checkBaseUrl(raw, variable) {
7273
- const value = raw.trim();
7274
- let url;
7275
- try {
7276
- url = new URL(value);
7277
- } catch {
7278
- throw new ConfigError(`${variable} must be an absolute https:// URL`);
7279
- }
7280
- const loopbackHttp = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
7281
- if (url.protocol !== "https:" && !loopbackHttp) {
7282
- throw new ConfigError(
7283
- `${variable} must use https:// (got ${redactUrl(value)}); plain http:// is only allowed for localhost, 127.0.0.1, or [::1]`
7284
- );
7285
- }
7286
- return value;
7287
- }
7288
7714
  function classifyMediaRef(value) {
7289
7715
  const v = value.trim();
7290
7716
  if (!v) throw new ValidationError("Media reference must not be empty");
@@ -7555,17 +7981,17 @@ function createHeygenClientFromEnv() {
7555
7981
  return new HeygenClient({ apiKey, baseUrl: checkBaseUrl(baseUrl, "HEYGEN_BASE_URL") });
7556
7982
  }
7557
7983
  async function uploadLocalFile2(client, filePath, logger) {
7558
- const abs = path32.resolve(filePath);
7984
+ const abs = path33.resolve(filePath);
7559
7985
  let stat;
7560
7986
  try {
7561
- stat = fs29.statSync(abs);
7987
+ stat = fs30.statSync(abs);
7562
7988
  } catch {
7563
7989
  throw new ValidationError(
7564
7990
  `File not found: ${filePath}. Use an https:// URL, asset:<asset_id>, or an existing file.`
7565
7991
  );
7566
7992
  }
7567
7993
  if (!stat.isFile()) throw new ValidationError(`Not a file: ${filePath}`);
7568
- const ext = path32.extname(abs).slice(1).toLowerCase();
7994
+ const ext = path33.extname(abs).slice(1).toLowerCase();
7569
7995
  const mimeType = ASSET_MIME_BY_EXT[ext];
7570
7996
  if (!mimeType) {
7571
7997
  throw new ValidationError(
@@ -7577,10 +8003,10 @@ async function uploadLocalFile2(client, filePath, logger) {
7577
8003
  `${filePath} is ${(stat.size / 1024 / 1024).toFixed(1)} MB; HeyGen asset upload accepts at most 32 MB. Host it at a public https:// URL instead.`
7578
8004
  );
7579
8005
  }
7580
- logger.info(`Uploading ${path32.basename(abs)} to HeyGen assets...`);
8006
+ logger.info(`Uploading ${path33.basename(abs)} to HeyGen assets...`);
7581
8007
  const res = await client.uploadAsset({
7582
- data: new Uint8Array(fs29.readFileSync(abs)),
7583
- filename: path32.basename(abs),
8008
+ data: new Uint8Array(fs30.readFileSync(abs)),
8009
+ filename: path33.basename(abs),
7584
8010
  mimeType
7585
8011
  });
7586
8012
  logger.debug(`Uploaded asset ${res.asset_id} (${res.mime_type}, ${res.size_bytes} bytes)`);
@@ -7624,7 +8050,7 @@ var MEDIA_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS]);
7624
8050
  var OUTPUT_EXTS = /* @__PURE__ */ new Set([...MEDIA_EXTS, "srt", "vtt"]);
7625
8051
  function inferExtension(url, fallback) {
7626
8052
  try {
7627
- const ext = path32.extname(new URL(url).pathname).slice(1).toLowerCase();
8053
+ const ext = path33.extname(new URL(url).pathname).slice(1).toLowerCase();
7628
8054
  return OUTPUT_EXTS.has(ext) ? ext : fallback;
7629
8055
  } catch {
7630
8056
  return fallback;
@@ -7662,11 +8088,11 @@ async function downloadOutput(url, dest, resumeHint) {
7662
8088
  }
7663
8089
  function defaultOutputPath(state, ext) {
7664
8090
  const lang = state.outputLanguage ? `-${safeSlug(state.outputLanguage)}` : "";
7665
- return path32.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
8091
+ return path33.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
7666
8092
  }
7667
8093
  function statOrUndefined(p) {
7668
8094
  try {
7669
- return fs29.statSync(p);
8095
+ return fs30.statSync(p);
7670
8096
  } catch {
7671
8097
  return void 0;
7672
8098
  }
@@ -7675,29 +8101,29 @@ function isDirectoryOutput2(output, target) {
7675
8101
  if (/[\\/]$/.test(output)) return true;
7676
8102
  const stat = statOrUndefined(target);
7677
8103
  if (stat) return stat.isDirectory();
7678
- return path32.extname(target) === "";
8104
+ return path33.extname(target) === "";
7679
8105
  }
7680
8106
  function withMediaExt(file, ext) {
7681
- const given = path32.extname(file).slice(1).toLowerCase();
8107
+ const given = path33.extname(file).slice(1).toLowerCase();
7682
8108
  const mismatch = VIDEO_EXTS.has(given) && AUDIO_EXTS.has(ext) || AUDIO_EXTS.has(given) && VIDEO_EXTS.has(ext);
7683
8109
  return mismatch ? `${file.slice(0, -(given.length + 1))}.${ext}` : file;
7684
8110
  }
7685
8111
  function outputPathFor(state, ext, output, count) {
7686
8112
  if (!output) return defaultOutputPath(state, ext);
7687
- const target = path32.resolve(output);
7688
- const givenExt = path32.extname(target);
8113
+ const target = path33.resolve(output);
8114
+ const givenExt = path33.extname(target);
7689
8115
  const isTemplate = count > 1 && MEDIA_EXTS.has(givenExt.slice(1).toLowerCase());
7690
8116
  if (isDirectoryOutput2(output, target) || count > 1 && !isTemplate) {
7691
- fs29.mkdirSync(target, { recursive: true });
7692
- return path32.join(target, path32.basename(defaultOutputPath(state, ext)));
8117
+ fs30.mkdirSync(target, { recursive: true });
8118
+ return path33.join(target, path33.basename(defaultOutputPath(state, ext)));
7693
8119
  }
7694
8120
  if (!isTemplate) return withMediaExt(target, ext);
7695
- const base = path32.basename(target, givenExt);
8121
+ const base = path33.basename(target, givenExt);
7696
8122
  const suffix = safeSlug(state.outputLanguage ?? state.id);
7697
- return withMediaExt(path32.join(path32.dirname(target), `${base}-${suffix}${givenExt}`), ext);
8123
+ return withMediaExt(path33.join(path33.dirname(target), `${base}-${suffix}${givenExt}`), ext);
7698
8124
  }
7699
8125
  function claimPath(dest, state, used) {
7700
- const ext = path32.extname(dest);
8126
+ const ext = path33.extname(dest);
7701
8127
  const stem = dest.slice(0, dest.length - ext.length);
7702
8128
  let candidate = dest;
7703
8129
  for (let n = 1; used.has(candidate.toLowerCase()); n++) {
@@ -7708,7 +8134,7 @@ function claimPath(dest, state, used) {
7708
8134
  return candidate;
7709
8135
  }
7710
8136
  function captionPathFor(dest, capExt) {
7711
- const stem = path32.join(path32.dirname(dest), path32.basename(dest, path32.extname(dest)));
8137
+ const stem = path33.join(path33.dirname(dest), path33.basename(dest, path33.extname(dest)));
7712
8138
  const capDest = `${stem}.${capExt}`;
7713
8139
  return capDest.toLowerCase() === dest.toLowerCase() ? `${stem}.captions.${capExt}` : capDest;
7714
8140
  }
@@ -8086,7 +8512,7 @@ function registerHeygenModelVideoCommand(parent) {
8086
8512
 
8087
8513
  // src/providers/heygen/commands/run.ts
8088
8514
  init_errors();
8089
- import path33 from "path";
8515
+ import path34 from "path";
8090
8516
  function extractJobIds(operation, data) {
8091
8517
  const op = HEYGEN_OPERATIONS[operation];
8092
8518
  if (!op) return [];
@@ -8124,7 +8550,7 @@ function registerHeygenRunCommand(parent) {
8124
8550
  return;
8125
8551
  }
8126
8552
  const ext = inferExtension(audioUrl, "mp3");
8127
- const dest = opts.output ? path33.resolve(opts.output) : path33.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
8553
+ const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
8128
8554
  await downloadOutput(audioUrl, dest, "Re-run the command to generate a fresh URL.");
8129
8555
  console.log(`Saved: ${dest}`);
8130
8556
  return;
@@ -8254,7 +8680,7 @@ function registerHeygenTranslateCommand(parent) {
8254
8680
  }
8255
8681
 
8256
8682
  // src/providers/heygen/commands/tts.ts
8257
- import path34 from "path";
8683
+ import path35 from "path";
8258
8684
  init_redact();
8259
8685
  function registerHeygenTtsCommand(parent) {
8260
8686
  parent.command("tts <text>").description("Text-to-speech with a HeyGen catalog voice (1-5000 characters)").requiredOption("--voice <voice-id>", "Voice ID from `heygen voices`").option("--ssml", "Treat the text as SSML").option("--speed <n>", "Speed multiplier 0.5-2.0").option("--language <code>", "Base language code, e.g. en (auto-detected when omitted)").option("--locale <tag>", "Locale tag for Starfish voices, e.g. zh-HK").option("--engine <e>", "starfish | orca | elevenlabs (defaults to the voice's engine)").option("--output <path>", "Output audio path").option("--no-download", "Print the audio URL instead of downloading").option("--json", "Print a JSON summary").option("-v, --verbose", "Verbose logging").action(async (text, opts) => {
@@ -8285,7 +8711,7 @@ function registerHeygenTtsCommand(parent) {
8285
8711
  }
8286
8712
  const ext = inferExtension(res.audio_url, "mp3");
8287
8713
  const name = `heygen-tts-${res.request_id ?? Date.now()}.${ext}`.replace(/[^\w.-]+/g, "-");
8288
- const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), name);
8714
+ const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), name);
8289
8715
  await downloadOutput(res.audio_url, dest, "Re-run the command to generate a fresh URL.");
8290
8716
  if (opts.json) console.log(JSON.stringify({ ...summary, file: dest }, null, 2));
8291
8717
  else console.log(`Saved: ${dest} (${res.duration}s, ${res.engine})`);
@@ -8404,8 +8830,8 @@ function registerHeygenCommands(program2) {
8404
8830
  }
8405
8831
 
8406
8832
  // src/providers/leonardo/commands/generate.ts
8407
- import fs30 from "fs";
8408
- import path35 from "path";
8833
+ import fs31 from "fs";
8834
+ import path36 from "path";
8409
8835
 
8410
8836
  // src/providers/leonardo/client.ts
8411
8837
  init_errors();
@@ -8483,11 +8909,11 @@ var LeonardoClient = class {
8483
8909
  }
8484
8910
  apiKey;
8485
8911
  baseUrl;
8486
- async request(path57, opts = {}) {
8912
+ async request(path58, opts = {}) {
8487
8913
  const base = this.baseUrl.replace(/\/$/, "");
8488
- const v2Override = path57.startsWith("v2:");
8914
+ const v2Override = path58.startsWith("v2:");
8489
8915
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
8490
- const finalPath = v2Override ? path57.slice(3) : path57;
8916
+ const finalPath = v2Override ? path58.slice(3) : path58;
8491
8917
  const url = new URL(finalBase + finalPath);
8492
8918
  if (opts.query) {
8493
8919
  for (const [k, v] of Object.entries(opts.query)) {
@@ -8506,14 +8932,14 @@ var LeonardoClient = class {
8506
8932
  timeoutMs: opts.timeoutMs ?? 12e4
8507
8933
  });
8508
8934
  }
8509
- get(path57, query, logger) {
8510
- return this.request(path57, { method: "GET", query, logger });
8935
+ get(path58, query, logger) {
8936
+ return this.request(path58, { method: "GET", query, logger });
8511
8937
  }
8512
- post(path57, body, logger) {
8513
- return this.request(path57, { method: "POST", body, logger });
8938
+ post(path58, body, logger) {
8939
+ return this.request(path58, { method: "POST", body, logger });
8514
8940
  }
8515
- delete(path57, logger) {
8516
- return this.request(path57, { method: "DELETE", logger });
8941
+ delete(path58, logger) {
8942
+ return this.request(path58, { method: "DELETE", logger });
8517
8943
  }
8518
8944
  };
8519
8945
  function createLeonardoClient() {
@@ -8524,7 +8950,7 @@ function createLeonardoClient() {
8524
8950
  function extFromUrl2(url, fallback = ".png") {
8525
8951
  try {
8526
8952
  const u = new URL(url);
8527
- const e = path35.extname(u.pathname);
8953
+ const e = path36.extname(u.pathname);
8528
8954
  return e.length > 0 ? e : fallback;
8529
8955
  } catch {
8530
8956
  return fallback;
@@ -8609,7 +9035,7 @@ function registerLeonardoGenerateCommand(parent) {
8609
9035
  if (!img) continue;
8610
9036
  const ext = extFromUrl2(img.url);
8611
9037
  const filename = autoFilename(generationId, i + 1, ext);
8612
- const filepath = path35.join(outDir, filename);
9038
+ const filepath = path36.join(outDir, filename);
8613
9039
  try {
8614
9040
  await downloadFile(img.url, filepath);
8615
9041
  saved.push(filepath);
@@ -8619,9 +9045,9 @@ function registerLeonardoGenerateCommand(parent) {
8619
9045
  }
8620
9046
  }
8621
9047
  if (opts.output && saved[0]) {
8622
- const dest = path35.resolve(opts.output);
8623
- fs30.mkdirSync(path35.dirname(dest), { recursive: true });
8624
- fs30.copyFileSync(saved[0], dest);
9048
+ const dest = path36.resolve(opts.output);
9049
+ fs31.mkdirSync(path36.dirname(dest), { recursive: true });
9050
+ fs31.copyFileSync(saved[0], dest);
8625
9051
  logger.success(`Copied to ${dest}`);
8626
9052
  }
8627
9053
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -8633,11 +9059,11 @@ Generated ${finalImages.length} image(s):`);
8633
9059
  }
8634
9060
 
8635
9061
  // src/providers/leonardo/commands/image-to-image.ts
8636
- import fs31 from "fs";
8637
- import path36 from "path";
9062
+ import fs32 from "fs";
9063
+ import path37 from "path";
8638
9064
  function extFromUrl3(url, fallback = ".png") {
8639
9065
  try {
8640
- return path36.extname(new URL(url).pathname) || fallback;
9066
+ return path37.extname(new URL(url).pathname) || fallback;
8641
9067
  } catch {
8642
9068
  return fallback;
8643
9069
  }
@@ -8699,7 +9125,7 @@ function registerLeonardoImageToImageCommand(parent) {
8699
9125
  if (!img) continue;
8700
9126
  const ext = extFromUrl3(img.url);
8701
9127
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
8702
- const filepath = path36.join(outDir, filename);
9128
+ const filepath = path37.join(outDir, filename);
8703
9129
  try {
8704
9130
  await downloadFile(img.url, filepath);
8705
9131
  saved.push(filepath);
@@ -8709,9 +9135,9 @@ function registerLeonardoImageToImageCommand(parent) {
8709
9135
  }
8710
9136
  }
8711
9137
  if (opts.output && saved[0]) {
8712
- const dest = path36.resolve(opts.output);
8713
- fs31.mkdirSync(path36.dirname(dest), { recursive: true });
8714
- fs31.copyFileSync(saved[0], dest);
9138
+ const dest = path37.resolve(opts.output);
9139
+ fs32.mkdirSync(path37.dirname(dest), { recursive: true });
9140
+ fs32.copyFileSync(saved[0], dest);
8715
9141
  logger.success(`Copied to ${dest}`);
8716
9142
  }
8717
9143
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -8723,7 +9149,7 @@ Generated ${finalImages.length} image(s):`);
8723
9149
  }
8724
9150
 
8725
9151
  // src/providers/leonardo/commands/video.ts
8726
- import path37 from "path";
9152
+ import path38 from "path";
8727
9153
  function v2DimsFor(resolution) {
8728
9154
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
8729
9155
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -8834,7 +9260,7 @@ async function pollAndDownload(client, jobId, opts) {
8834
9260
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
8835
9261
  process.exit(1);
8836
9262
  }
8837
- const outPath = opts.output ?? path37.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
9263
+ const outPath = opts.output ?? path38.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
8838
9264
  await downloadFile(videoUrl2, outPath);
8839
9265
  logger.success(`Saved ${outPath}`);
8840
9266
  await maybeDownloadThumb(gen, outPath, {
@@ -8940,7 +9366,7 @@ function registerLeonardoModelsCommand(parent) {
8940
9366
  }
8941
9367
 
8942
9368
  // src/providers/leonardo/commands/status.ts
8943
- import path38 from "path";
9369
+ import path39 from "path";
8944
9370
  var VIDEO_KEYS = ["motionMP4URL", "url"];
8945
9371
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
8946
9372
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -9005,7 +9431,7 @@ function registerLeonardoStatusCommand(parent) {
9005
9431
  const outDir = getOutputDir();
9006
9432
  let idx = 0;
9007
9433
  for (const url of videos) {
9008
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path38.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
9434
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
9009
9435
  await downloadFile(url, dest);
9010
9436
  logger.success(`Saved ${dest}`);
9011
9437
  await maybeDownloadThumb(gen, dest, {
@@ -9018,7 +9444,7 @@ function registerLeonardoStatusCommand(parent) {
9018
9444
  for (const url of images) {
9019
9445
  const m = url.match(IMAGE_EXT_RE2);
9020
9446
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
9021
- const dest = path38.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
9447
+ const dest = path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
9022
9448
  await downloadFile(url, dest);
9023
9449
  logger.success(`Saved ${dest}`);
9024
9450
  idx++;
@@ -9175,8 +9601,8 @@ function sleep3(ms) {
9175
9601
  }
9176
9602
 
9177
9603
  // src/providers/minimax/generators/music.ts
9178
- import fs32 from "fs";
9179
- import path39 from "path";
9604
+ import fs33 from "fs";
9605
+ import path40 from "path";
9180
9606
  async function generateMinimaxMusic(opts) {
9181
9607
  const {
9182
9608
  apiKey,
@@ -9221,14 +9647,14 @@ async function generateMinimaxMusic(opts) {
9221
9647
  }
9222
9648
  const durationMs = resp.extra_info?.music_duration ?? 0;
9223
9649
  const outDir = getOutputDir();
9224
- const dest = path39.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
9650
+ const dest = path40.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
9225
9651
  try {
9226
9652
  if (audioData.startsWith("http")) {
9227
9653
  const bytes = await fetchBytes(audioData);
9228
- fs32.writeFileSync(dest, bytes);
9654
+ fs33.writeFileSync(dest, bytes);
9229
9655
  } else {
9230
9656
  const audioBytes = Buffer.from(audioData, "hex");
9231
- fs32.writeFileSync(dest, audioBytes);
9657
+ fs33.writeFileSync(dest, audioBytes);
9232
9658
  }
9233
9659
  } catch (e) {
9234
9660
  return {
@@ -9239,8 +9665,8 @@ async function generateMinimaxMusic(opts) {
9239
9665
  const durSec = durationMs / 1e3;
9240
9666
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
9241
9667
  if (output) {
9242
- fs32.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
9243
- fs32.copyFileSync(dest, output);
9668
+ fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
9669
+ fs33.copyFileSync(dest, output);
9244
9670
  }
9245
9671
  return { status: "success", generatedAudio: dest, durationMs, model };
9246
9672
  }
@@ -9314,8 +9740,8 @@ Generated music: ${result.generatedAudio}`);
9314
9740
  init_errors();
9315
9741
 
9316
9742
  // src/providers/minimax/generators/speech.ts
9317
- import fs33 from "fs";
9318
- import path40 from "path";
9743
+ import fs34 from "fs";
9744
+ import path41 from "path";
9319
9745
  async function generateMinimaxSpeech(opts) {
9320
9746
  const {
9321
9747
  apiKey,
@@ -9362,12 +9788,12 @@ async function generateMinimaxSpeech(opts) {
9362
9788
  const audioBytes = Buffer.from(audioHex, "hex");
9363
9789
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
9364
9790
  const outDir = getOutputDir();
9365
- const dest = path40.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
9366
- fs33.writeFileSync(dest, audioBytes);
9791
+ const dest = path41.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
9792
+ fs34.writeFileSync(dest, audioBytes);
9367
9793
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
9368
9794
  if (output) {
9369
- fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
9370
- fs33.copyFileSync(dest, output);
9795
+ fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
9796
+ fs34.copyFileSync(dest, output);
9371
9797
  }
9372
9798
  return { status: "success", generatedAudio: dest, model };
9373
9799
  }
@@ -9414,8 +9840,8 @@ Generated audio: ${result.generatedAudio}`);
9414
9840
  init_errors();
9415
9841
 
9416
9842
  // src/providers/minimax/generators/video.ts
9417
- import fs34 from "fs";
9418
- import path41 from "path";
9843
+ import fs35 from "fs";
9844
+ import path42 from "path";
9419
9845
  async function generateMinimaxVideo(opts) {
9420
9846
  const {
9421
9847
  apiKey,
@@ -9461,18 +9887,18 @@ async function generateMinimaxVideo(opts) {
9461
9887
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
9462
9888
  }
9463
9889
  const outDir = getOutputDir();
9464
- const dest = path41.join(outDir, `minimax_video_${Date.now()}.mp4`);
9890
+ const dest = path42.join(outDir, `minimax_video_${Date.now()}.mp4`);
9465
9891
  try {
9466
9892
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
9467
9893
  } catch (e) {
9468
9894
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
9469
9895
  }
9470
9896
  const elapsed = (Date.now() - start) / 1e3;
9471
- const stat = fs34.statSync(dest);
9897
+ const stat = fs35.statSync(dest);
9472
9898
  const fileSizeMb = stat.size / (1024 * 1024);
9473
9899
  if (output) {
9474
- fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
9475
- fs34.copyFileSync(dest, output);
9900
+ fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
9901
+ fs35.copyFileSync(dest, output);
9476
9902
  }
9477
9903
  await maybeDownloadThumb(pollResult, dest, {
9478
9904
  skip: thumb === false,
@@ -9534,8 +9960,8 @@ Generated video: ${result.generatedVideo}`);
9534
9960
 
9535
9961
  // src/providers/minimax/generators/image.ts
9536
9962
  init_errors();
9537
- import fs35 from "fs";
9538
- import path42 from "path";
9963
+ import fs36 from "fs";
9964
+ import path43 from "path";
9539
9965
  async function generateMinimaxImage(opts) {
9540
9966
  const {
9541
9967
  apiKey,
@@ -9573,8 +9999,8 @@ async function generateMinimaxImage(opts) {
9573
9999
  try {
9574
10000
  const bytes = await fetchBytes(url);
9575
10001
  const fname = `minimax_image_${Date.now()}_${i}.png`;
9576
- const dest = path42.join(outDir, fname);
9577
- fs35.writeFileSync(dest, bytes);
10002
+ const dest = path43.join(outDir, fname);
10003
+ fs36.writeFileSync(dest, bytes);
9578
10004
  savedFiles.push(dest);
9579
10005
  logger?.success(`Saved: ${dest}`);
9580
10006
  } catch (e) {
@@ -9585,8 +10011,8 @@ async function generateMinimaxImage(opts) {
9585
10011
  }
9586
10012
  }
9587
10013
  if (output && savedFiles[0]) {
9588
- fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
9589
- fs35.copyFileSync(savedFiles[0], output);
10014
+ fs36.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
10015
+ fs36.copyFileSync(savedFiles[0], output);
9590
10016
  }
9591
10017
  return { status: "success", generatedImages: savedFiles, model };
9592
10018
  }
@@ -9625,8 +10051,8 @@ Generated ${finalImages.length} image(s):`);
9625
10051
 
9626
10052
  // src/providers/minimax/commands/image-to-image.ts
9627
10053
  init_errors();
9628
- import fs36 from "fs";
9629
- import path43 from "path";
10054
+ import fs37 from "fs";
10055
+ import path44 from "path";
9630
10056
  function registerMinimaxImageToImageCommand(parent) {
9631
10057
  parent.command("image-to-image").alias("i2i").description(
9632
10058
  "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."
@@ -9681,8 +10107,8 @@ function registerMinimaxImageToImageCommand(parent) {
9681
10107
  if (!url) continue;
9682
10108
  try {
9683
10109
  const bytes = await fetchBytes(url);
9684
- const dest = path43.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
9685
- fs36.writeFileSync(dest, bytes);
10110
+ const dest = path44.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
10111
+ fs37.writeFileSync(dest, bytes);
9686
10112
  saved.push(dest);
9687
10113
  logger.success(`Saved: ${dest}`);
9688
10114
  } catch (e) {
@@ -9693,8 +10119,8 @@ function registerMinimaxImageToImageCommand(parent) {
9693
10119
  }
9694
10120
  }
9695
10121
  if (opts.output && saved[0]) {
9696
- fs36.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
9697
- fs36.copyFileSync(saved[0], opts.output);
10122
+ fs37.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
10123
+ fs37.copyFileSync(saved[0], opts.output);
9698
10124
  logger.success(`Copied to: ${opts.output}`);
9699
10125
  }
9700
10126
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -9828,12 +10254,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
9828
10254
  }
9829
10255
 
9830
10256
  // src/providers/openai/openai-audio.ts
9831
- import fs38 from "fs";
9832
- import path45 from "path";
10257
+ import fs39 from "fs";
10258
+ import path46 from "path";
9833
10259
 
9834
10260
  // src/providers/openai/client.ts
9835
- import fs37 from "fs";
9836
- import path44 from "path";
10261
+ import fs38 from "fs";
10262
+ import path45 from "path";
9837
10263
  init_errors();
9838
10264
  var DEFAULT_TIMEOUT_MS4 = 24e4;
9839
10265
  function requireOpenAIKey() {
@@ -9901,10 +10327,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
9901
10327
  return Buffer.from(await res.arrayBuffer());
9902
10328
  }
9903
10329
  function fileFromPath(filePath) {
9904
- const abs = path44.resolve(filePath);
9905
- if (!fs37.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
9906
- const bytes = fs37.readFileSync(abs);
9907
- return new File([new Uint8Array(bytes)], path44.basename(abs));
10330
+ const abs = path45.resolve(filePath);
10331
+ if (!fs38.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
10332
+ const bytes = fs38.readFileSync(abs);
10333
+ return new File([new Uint8Array(bytes)], path45.basename(abs));
9908
10334
  }
9909
10335
 
9910
10336
  // src/providers/openai/openai-audio.ts
@@ -9943,7 +10369,7 @@ async function resolveAudioReferenceDataUrl(ref) {
9943
10369
  }
9944
10370
  function audioMimeFromReference(ref) {
9945
10371
  const pathname = new URL(ref).pathname;
9946
- const ext = path45.extname(pathname).toLowerCase();
10372
+ const ext = path46.extname(pathname).toLowerCase();
9947
10373
  switch (ext) {
9948
10374
  case ".mp3":
9949
10375
  return "audio/mpeg";
@@ -9962,12 +10388,12 @@ function audioMimeFromReference(ref) {
9962
10388
  async function generateOpenAISpeech(opts) {
9963
10389
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
9964
10390
  const ext = extFromAudioFormat(opts.outputFormat);
9965
- const dest = path45.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
9966
- fs38.writeFileSync(dest, bytes);
10391
+ const dest = path46.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
10392
+ fs39.writeFileSync(dest, bytes);
9967
10393
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
9968
10394
  if (opts.output) {
9969
- fs38.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
9970
- fs38.copyFileSync(dest, opts.output);
10395
+ fs39.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
10396
+ fs39.copyFileSync(dest, opts.output);
9971
10397
  }
9972
10398
  return dest;
9973
10399
  }
@@ -9989,9 +10415,9 @@ async function transcribeOpenAIAudio(opts) {
9989
10415
  );
9990
10416
  const text = typeof data.text === "string" ? data.text : void 0;
9991
10417
  const ext = opts.format === "text" ? "txt" : "json";
9992
- const dest = opts.output ? path45.resolve(opts.output) : path45.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
9993
- fs38.mkdirSync(path45.dirname(dest), { recursive: true });
9994
- fs38.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
10418
+ const dest = opts.output ? path46.resolve(opts.output) : path46.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
10419
+ fs39.mkdirSync(path46.dirname(dest), { recursive: true });
10420
+ fs39.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
9995
10421
  opts.logger?.success(`Saved transcript: ${dest}`);
9996
10422
  return { path: dest, text };
9997
10423
  }
@@ -10018,9 +10444,9 @@ Generated audio: ${dest}`);
10018
10444
  }
10019
10445
 
10020
10446
  // src/providers/openai/codex-image-driver.ts
10021
- import fs39 from "fs";
10022
- import os3 from "os";
10023
- import path46 from "path";
10447
+ import fs40 from "fs";
10448
+ import os4 from "os";
10449
+ import path47 from "path";
10024
10450
  import { execa } from "execa";
10025
10451
  init_errors();
10026
10452
  var defaultRunner = async (cmd, args, opts) => {
@@ -10069,12 +10495,12 @@ function sanitizeCodexEnv() {
10069
10495
  async function runCodexImageGeneration(opts) {
10070
10496
  const runner = opts.runner ?? defaultRunner;
10071
10497
  const ext = extFromImageFormat(opts.outputFormat);
10072
- const dest = opts.output ? path46.resolve(opts.output) : path46.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
10073
- fs39.mkdirSync(path46.dirname(dest), { recursive: true });
10074
- const tmp = fs39.mkdtempSync(path46.join(os3.tmpdir(), "multix-codex-image-"));
10075
- const schema = path46.join(tmp, "schema.json");
10076
- const result = path46.join(tmp, "result.json");
10077
- fs39.writeFileSync(
10498
+ const dest = opts.output ? path47.resolve(opts.output) : path47.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
10499
+ fs40.mkdirSync(path47.dirname(dest), { recursive: true });
10500
+ const tmp = fs40.mkdtempSync(path47.join(os4.tmpdir(), "multix-codex-image-"));
10501
+ const schema = path47.join(tmp, "schema.json");
10502
+ const result = path47.join(tmp, "result.json");
10503
+ fs40.writeFileSync(
10078
10504
  schema,
10079
10505
  JSON.stringify({
10080
10506
  type: "object",
@@ -10104,7 +10530,7 @@ async function runCodexImageGeneration(opts) {
10104
10530
  if (res.exitCode !== 0) {
10105
10531
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
10106
10532
  }
10107
- if (!fs39.existsSync(dest) || fs39.statSync(dest).size === 0) {
10533
+ if (!fs40.existsSync(dest) || fs40.statSync(dest).size === 0) {
10108
10534
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
10109
10535
  }
10110
10536
  return { path: dest };
@@ -10121,8 +10547,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
10121
10547
 
10122
10548
  // src/providers/openai/openai-image.ts
10123
10549
  init_errors();
10124
- import fs40 from "fs";
10125
- import path47 from "path";
10550
+ import fs41 from "fs";
10551
+ import path48 from "path";
10126
10552
  function buildImageGenerationBody(opts) {
10127
10553
  return {
10128
10554
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -10155,14 +10581,14 @@ function saveOpenAIImages(opts) {
10155
10581
  for (let i = 0; i < items.length; i++) {
10156
10582
  const b64 = items[i]?.b64_json;
10157
10583
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
10158
- const dest = path47.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
10159
- fs40.writeFileSync(dest, Buffer.from(b64, "base64"));
10584
+ const dest = path48.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
10585
+ fs41.writeFileSync(dest, Buffer.from(b64, "base64"));
10160
10586
  saved.push(dest);
10161
10587
  opts.logger?.success(`Saved: ${dest}`);
10162
10588
  }
10163
10589
  if (opts.output && saved[0]) {
10164
- fs40.mkdirSync(path47.dirname(path47.resolve(opts.output)), { recursive: true });
10165
- fs40.copyFileSync(saved[0], opts.output);
10590
+ fs41.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
10591
+ fs41.copyFileSync(saved[0], opts.output);
10166
10592
  opts.logger?.success(`Copied to: ${opts.output}`);
10167
10593
  }
10168
10594
  return saved;
@@ -10171,7 +10597,7 @@ async function resolveImageRefBytes(ref) {
10171
10597
  if (isHttpUrl(ref)) {
10172
10598
  const bytes2 = await fetchBytes(ref);
10173
10599
  const pathname = new URL(ref).pathname;
10174
- const filename = path47.basename(pathname) || "reference.png";
10600
+ const filename = path48.basename(pathname) || "reference.png";
10175
10601
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
10176
10602
  }
10177
10603
  const file = fileFromPath(ref);
@@ -10216,7 +10642,7 @@ function sniffImageMime2(buf, name) {
10216
10642
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
10217
10643
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
10218
10644
  return "image/webp";
10219
- const ext = path47.extname(name).toLowerCase();
10645
+ const ext = path48.extname(name).toLowerCase();
10220
10646
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
10221
10647
  if (ext === ".webp") return "image/webp";
10222
10648
  return "image/png";
@@ -10360,8 +10786,8 @@ function registerOpenAICommands(program2) {
10360
10786
  }
10361
10787
 
10362
10788
  // src/providers/openrouter/client.ts
10363
- import fs41 from "fs";
10364
- import path48 from "path";
10789
+ import fs42 from "fs";
10790
+ import path49 from "path";
10365
10791
  init_errors();
10366
10792
 
10367
10793
  // src/providers/openrouter/payload.ts
@@ -10525,8 +10951,8 @@ async function generateOpenRouterImage(opts) {
10525
10951
  if (!url) continue;
10526
10952
  try {
10527
10953
  const bytes = await fetchBytes(url);
10528
- const dest = path48.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
10529
- fs41.writeFileSync(dest, bytes);
10954
+ const dest = path49.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
10955
+ fs42.writeFileSync(dest, bytes);
10530
10956
  savedFiles.push(dest);
10531
10957
  logger?.success(`Saved: ${dest}`);
10532
10958
  } catch (e) {
@@ -10537,8 +10963,8 @@ async function generateOpenRouterImage(opts) {
10537
10963
  }
10538
10964
  }
10539
10965
  if (output && savedFiles[0]) {
10540
- fs41.mkdirSync(path48.dirname(path48.resolve(output)), { recursive: true });
10541
- fs41.writeFileSync(output, fs41.readFileSync(savedFiles[0]));
10966
+ fs42.mkdirSync(path49.dirname(path49.resolve(output)), { recursive: true });
10967
+ fs42.writeFileSync(output, fs42.readFileSync(savedFiles[0]));
10542
10968
  }
10543
10969
  return { status: "success", generatedImages: savedFiles, model: usedModel };
10544
10970
  }
@@ -10579,8 +11005,8 @@ Generated ${finalImages.length} image(s):`);
10579
11005
  }
10580
11006
 
10581
11007
  // src/providers/openrouter/commands/image-to-image.ts
10582
- import fs42 from "fs";
10583
- import path49 from "path";
11008
+ import fs43 from "fs";
11009
+ import path50 from "path";
10584
11010
  init_errors();
10585
11011
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
10586
11012
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
@@ -10672,14 +11098,14 @@ async function runImageToImage(opts) {
10672
11098
  const url = parsed.urls[i];
10673
11099
  if (!url) continue;
10674
11100
  const bytes = await fetchBytes(url);
10675
- const dest = path49.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
10676
- fs42.writeFileSync(dest, bytes);
11101
+ const dest = path50.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
11102
+ fs43.writeFileSync(dest, bytes);
10677
11103
  saved.push(dest);
10678
11104
  opts.logger.success(`Saved: ${dest}`);
10679
11105
  }
10680
11106
  if (opts.output && saved[0]) {
10681
- fs42.mkdirSync(path49.dirname(path49.resolve(opts.output)), { recursive: true });
10682
- fs42.copyFileSync(saved[0], opts.output);
11107
+ fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
11108
+ fs43.copyFileSync(saved[0], opts.output);
10683
11109
  opts.logger.success(`Copied to: ${opts.output}`);
10684
11110
  }
10685
11111
  return saved;
@@ -10724,13 +11150,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
10724
11150
  }
10725
11151
 
10726
11152
  // src/providers/openrouter/commands/video-status.ts
10727
- import fs43 from "fs";
10728
- import path50 from "path";
11153
+ import fs44 from "fs";
11154
+ import path51 from "path";
10729
11155
  async function downloadOpenRouterVideo(opts) {
10730
11156
  const { jobId, logger, source } = opts;
10731
11157
  const apiKey = requireOpenRouterKey();
10732
11158
  const url = videoContentUrl(jobId);
10733
- const dest = path50.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
11159
+ const dest = path51.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
10734
11160
  const { response: r } = await fetchWithValidatedRedirects(url, {
10735
11161
  headers: { Authorization: `Bearer ${apiKey}` }
10736
11162
  });
@@ -10742,14 +11168,14 @@ async function downloadOpenRouterVideo(opts) {
10742
11168
  logger.error("Empty response body");
10743
11169
  process.exit(1);
10744
11170
  }
10745
- fs43.mkdirSync(path50.dirname(dest), { recursive: true });
10746
- const ws = fs43.createWriteStream(dest);
11171
+ fs44.mkdirSync(path51.dirname(dest), { recursive: true });
11172
+ const ws = fs44.createWriteStream(dest);
10747
11173
  const { pipeline: pipeline2 } = await import("stream/promises");
10748
11174
  await pipeline2(r.body, ws);
10749
11175
  logger.success(`Saved ${dest}`);
10750
11176
  if (opts.output) {
10751
- fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
10752
- fs43.copyFileSync(dest, opts.output);
11177
+ fs44.mkdirSync(path51.dirname(path51.resolve(opts.output)), { recursive: true });
11178
+ fs44.copyFileSync(dest, opts.output);
10753
11179
  logger.success(`Copied to ${opts.output}`);
10754
11180
  }
10755
11181
  await maybeDownloadThumb(source, dest, {
@@ -10947,8 +11373,8 @@ function parseModelRef(ref, explicitVersion) {
10947
11373
  }
10948
11374
  return { kind: "version", version: trimmed };
10949
11375
  }
10950
- const [path57, inlineVersion, ...rest] = trimmed.split(":");
10951
- const [owner, name, ...extra] = (path57 ?? "").split("/");
11376
+ const [path58, inlineVersion, ...rest] = trimmed.split(":");
11377
+ const [owner, name, ...extra] = (path58 ?? "").split("/");
10952
11378
  if (rest.length > 0 || extra.length > 0 || !owner || !name) {
10953
11379
  throw new ValidationError(
10954
11380
  `Invalid Replicate model "${ref}" \u2014 expected owner/name, owner/name:version, or a 64-char version id`
@@ -11108,9 +11534,9 @@ var ReplicateClient = class {
11108
11534
  return { accept: "application/json", authorization: `Bearer ${this.apiKey}`, ...extra };
11109
11535
  }
11110
11536
  /** Raw request: throws HttpError so polling can classify transient failures. */
11111
- raw(path57, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
11537
+ raw(path58, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
11112
11538
  return fetchJson({
11113
- url: `${this.baseUrl}${path57}`,
11539
+ url: `${this.baseUrl}${path58}`,
11114
11540
  method,
11115
11541
  headers: this.headers(headers2),
11116
11542
  body,
@@ -11122,7 +11548,7 @@ var ReplicateClient = class {
11122
11548
  async createPrediction(opts) {
11123
11549
  const { model, input } = opts;
11124
11550
  const isOfficial = model.kind === "official";
11125
- const path57 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
11551
+ const path58 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
11126
11552
  const body = isOfficial ? { input } : { version: model.version, input };
11127
11553
  const subject = isOfficial ? `model ${model.owner}/${model.name}` : `model version ${model.version}`;
11128
11554
  const headers2 = {};
@@ -11134,7 +11560,7 @@ var ReplicateClient = class {
11134
11560
  }
11135
11561
  for (let attempt = 0; ; attempt++) {
11136
11562
  try {
11137
- const res = await this.raw(path57, "POST", body, headers2, timeoutMs);
11563
+ const res = await this.raw(path58, "POST", body, headers2, timeoutMs);
11138
11564
  return validatePrediction(res);
11139
11565
  } catch (error) {
11140
11566
  if (error instanceof HttpError && error.status === 429 && attempt < CREATE_MAX_RETRIES) {
@@ -11297,145 +11723,33 @@ import { InvalidArgumentError, Option as Option2 } from "commander";
11297
11723
 
11298
11724
  // src/providers/replicate/node-outputs.ts
11299
11725
  init_errors();
11300
- import fs44 from "fs";
11301
- import path51 from "path";
11726
+ import fs45 from "fs";
11727
+ import path52 from "path";
11302
11728
  init_redact();
11303
11729
 
11304
11730
  // src/providers/replicate/output.ts
11305
11731
  init_redact();
11306
-
11307
- // src/core/url-safety.ts
11308
- init_errors();
11309
- function ipv4Octets(host) {
11310
- const parts = host.split(".");
11311
- if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
11312
- const octets = parts.map(Number);
11313
- return octets.every((o) => o <= 255) ? octets : void 0;
11314
- }
11315
- function isNonPublicIpv4([a = 0, b = 0, c2 = 0]) {
11316
- return a === 0 || // "this network" / unspecified
11317
- a === 10 || a === 127 || // loopback
11318
- a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
11319
- a === 169 && b === 254 || // link-local (cloud metadata)
11320
- a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 && c2 === 0 || // IETF protocol assignments
11321
- a === 192 && b === 0 && c2 === 2 || // TEST-NET-1
11322
- a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || // benchmarking
11323
- a === 198 && b === 51 && c2 === 100 || // TEST-NET-2
11324
- a === 203 && b === 0 && c2 === 113 || // TEST-NET-3
11325
- a >= 224;
11326
- }
11327
- function ipv6Groups(host) {
11328
- if (!/^[0-9a-f:.]+$/i.test(host) || !host.includes(":")) return void 0;
11329
- let text = host;
11330
- const tail = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
11331
- if (tail) {
11332
- const octets = ipv4Octets(tail[2]);
11333
- if (!octets) return void 0;
11334
- const [a = 0, b = 0, c2 = 0, d = 0] = octets;
11335
- text = `${tail[1]}${(a << 8 | b).toString(16)}:${(c2 << 8 | d).toString(16)}`;
11336
- }
11337
- const halves = text.split("::");
11338
- if (halves.length > 2) return void 0;
11339
- const parse = (s) => s === "" ? [] : s.split(":");
11340
- const head = parse(halves[0]);
11341
- const rest = halves.length === 2 ? parse(halves[1]) : [];
11342
- const missing = 8 - head.length - rest.length;
11343
- if (halves.length === 2 ? missing < 1 : missing !== 0) return void 0;
11344
- const all = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...rest];
11345
- if (!all.every((g) => /^[0-9a-f]{1,4}$/i.test(g))) return void 0;
11346
- return all.map((g) => Number.parseInt(g, 16));
11347
- }
11348
- function embeddedIpv4(hi, lo) {
11349
- return [hi >> 8, hi & 255, lo >> 8, lo & 255];
11350
- }
11351
- function isNonPublicIpv6(groups) {
11352
- const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
11353
- const zeroUpTo = (n) => groups.slice(0, n).every((g) => g === 0);
11354
- if (zeroUpTo(5) && g5 === 65535) return isNonPublicIpv4(embeddedIpv4(g6, g7));
11355
- if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
11356
- return isNonPublicIpv4(embeddedIpv4(g6, g7));
11357
- }
11358
- if (g0 === 8194) return isNonPublicIpv4(embeddedIpv4(g1, g2));
11359
- return g0 === 0 || // unspecified (::), loopback (::1), IPv4-compatible and other reserved
11360
- g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0 || // discard-only 100::/64
11361
- (g0 & 65024) === 64512 || // unique local fc00::/7
11362
- (g0 & 65472) === 65152 || // link-local fe80::/10
11363
- (g0 & 65472) === 65216 || // deprecated site-local fec0::/10
11364
- (g0 & 65280) === 65280;
11365
- }
11366
- function bareHost(hostname) {
11367
- const host = hostname.toLowerCase().replace(/\.$/, "");
11368
- return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
11369
- }
11370
- function isLoopbackHost(hostname) {
11371
- const host = bareHost(hostname);
11372
- if (host === "localhost" || host.endsWith(".localhost")) return true;
11373
- const octets = ipv4Octets(host);
11374
- if (octets) return octets[0] === 127;
11375
- const groups = ipv6Groups(host);
11376
- if (!groups) return false;
11377
- if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535) {
11378
- return groups[6] !== void 0 && groups[6] >> 8 === 127;
11379
- }
11380
- return groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1;
11381
- }
11382
- function isNonPublicHost(hostname) {
11383
- const host = bareHost(hostname);
11384
- if (host === "localhost" || host.endsWith(".localhost")) return true;
11385
- const octets = ipv4Octets(host);
11386
- if (octets) return isNonPublicIpv4(octets);
11387
- const groups = ipv6Groups(host);
11388
- return groups !== void 0 && isNonPublicIpv6(groups);
11389
- }
11390
- function checkUrlSafety(url, opts) {
11391
- const action = opts.action ?? "download from";
11392
- let parsed;
11393
- try {
11394
- parsed = new URL(url);
11395
- } catch {
11396
- throw new ProviderError(`Refusing to ${action} a malformed URL`, opts.provider);
11397
- }
11398
- const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
11399
- const localOk = opts.allowLocalhostHttp === true && isLoopbackHost(host);
11400
- const httpOk = parsed.protocol === "https:" || localOk && parsed.protocol === "http:";
11401
- if (!httpOk) {
11402
- throw new ProviderError(
11403
- `Refusing to ${action} ${host || "this URL"}: only https:// URLs are allowed`,
11404
- opts.provider
11405
- );
11406
- }
11407
- if (!localOk && isNonPublicHost(host)) {
11408
- throw new ProviderError(
11409
- `Refusing to ${action} ${host}: it points to a local or private address`,
11410
- opts.provider
11411
- );
11412
- }
11413
- const trusted = (opts.trustedHosts ?? []).some((t) => host === t || host.endsWith(`.${t}`));
11414
- return { host, trusted };
11415
- }
11416
-
11417
- // src/providers/replicate/output.ts
11418
11732
  var HTTP_RE = /^https?:\/\//i;
11419
11733
  var DATA_URI_RE = /^data:[^,]*,/i;
11420
11734
  function isMediaRef(value) {
11421
11735
  return typeof value === "string" && (HTTP_RE.test(value) || DATA_URI_RE.test(value));
11422
11736
  }
11423
11737
  var REPLICATE_DELIVERY_HOST = "replicate.delivery";
11424
- function checkOutputUrl(url) {
11738
+ function checkOutputUrl2(url) {
11425
11739
  return checkUrlSafety(url, { provider: "replicate", trustedHosts: [REPLICATE_DELIVERY_HOST] });
11426
11740
  }
11427
11741
  function extractOutput(output) {
11428
11742
  const result = { media: [], texts: [] };
11429
11743
  const seen = /* @__PURE__ */ new Set();
11430
- const visit = (value, path57) => {
11744
+ const visit = (value, path58) => {
11431
11745
  if (value === null || value === void 0) return;
11432
11746
  if (typeof value === "string") {
11433
- if (isMediaRef(value)) result.media.push({ url: value, path: path57 || "output" });
11434
- else if (value.length > 0) result.texts.push(path57 ? `${path57}: ${value}` : value);
11747
+ if (isMediaRef(value)) result.media.push({ url: value, path: path58 || "output" });
11748
+ else if (value.length > 0) result.texts.push(path58 ? `${path58}: ${value}` : value);
11435
11749
  return;
11436
11750
  }
11437
11751
  if (typeof value !== "object") {
11438
- result.texts.push(path57 ? `${path57}: ${String(value)}` : String(value));
11752
+ result.texts.push(path58 ? `${path58}: ${String(value)}` : String(value));
11439
11753
  return;
11440
11754
  }
11441
11755
  if (seen.has(value)) return;
@@ -11443,14 +11757,14 @@ function extractOutput(output) {
11443
11757
  if (Array.isArray(value)) {
11444
11758
  if (value.length > 0 && value.every((v) => typeof v === "string" && !isMediaRef(v))) {
11445
11759
  const joined = value.join("");
11446
- if (joined.length > 0) result.texts.push(path57 ? `${path57}: ${joined}` : joined);
11760
+ if (joined.length > 0) result.texts.push(path58 ? `${path58}: ${joined}` : joined);
11447
11761
  return;
11448
11762
  }
11449
- value.forEach((item, i) => visit(item, `${path57}[${i}]`));
11763
+ value.forEach((item, i) => visit(item, `${path58}[${i}]`));
11450
11764
  return;
11451
11765
  }
11452
11766
  for (const [key, val] of Object.entries(value)) {
11453
- visit(val, path57 ? `${path57}.${key}` : key);
11767
+ visit(val, path58 ? `${path58}.${key}` : key);
11454
11768
  }
11455
11769
  };
11456
11770
  visit(output, "");
@@ -11568,16 +11882,16 @@ var DOWNLOAD_TIMEOUT_MS = 3e5;
11568
11882
  function isDirTarget(output) {
11569
11883
  if (/[\\/]$/.test(output)) return true;
11570
11884
  try {
11571
- return fs44.statSync(output).isDirectory();
11885
+ return fs45.statSync(output).isDirectory();
11572
11886
  } catch {
11573
11887
  return false;
11574
11888
  }
11575
11889
  }
11576
11890
  function resolveDestination(predictionId, index, count, ext, opts) {
11577
- if (!opts.output) return path51.join(opts.outDir, outputFilename(predictionId, index, ext));
11578
- const output = path51.resolve(opts.output);
11579
- if (isDirTarget(opts.output)) return path51.join(output, outputFilename(predictionId, index, ext));
11580
- const givenExt = path51.extname(output);
11891
+ if (!opts.output) return path52.join(opts.outDir, outputFilename(predictionId, index, ext));
11892
+ const output = path52.resolve(opts.output);
11893
+ if (isDirTarget(opts.output)) return path52.join(output, outputFilename(predictionId, index, ext));
11894
+ const givenExt = path52.extname(output);
11581
11895
  if (count === 1) return givenExt ? output : `${output}${ext}`;
11582
11896
  const base = givenExt ? output.slice(0, -givenExt.length) : output;
11583
11897
  return `${base}-${String(index + 1).padStart(2, "0")}${ext}`;
@@ -11595,8 +11909,8 @@ async function saveOne(item, predictionId, index, count, opts) {
11595
11909
  extFromUrl4(item.url) ?? ".bin",
11596
11910
  opts
11597
11911
  );
11598
- fs44.mkdirSync(path51.dirname(dest), { recursive: true });
11599
- fs44.writeFileSync(dest, bytes);
11912
+ fs45.mkdirSync(path52.dirname(dest), { recursive: true });
11913
+ fs45.writeFileSync(dest, bytes);
11600
11914
  return dest;
11601
11915
  }
11602
11916
  const warned2 = opts.warnedHosts;
@@ -11610,7 +11924,7 @@ async function saveOne(item, predictionId, index, count, opts) {
11610
11924
  maxBytes: opts.maxBytes,
11611
11925
  fetch: opts.fetch,
11612
11926
  validateUrl: (url) => {
11613
- const { host, trusted } = checkOutputUrl(url);
11927
+ const { host, trusted } = checkOutputUrl2(url);
11614
11928
  if (!trusted && !warned2.has(host)) {
11615
11929
  warned2.add(host);
11616
11930
  opts.logger.warn(
@@ -11811,8 +12125,8 @@ function registerReplicateImageCommand(parent) {
11811
12125
 
11812
12126
  // src/providers/replicate/node-input-files.ts
11813
12127
  init_errors();
11814
- import fs45 from "fs";
11815
- import path52 from "path";
12128
+ import fs46 from "fs";
12129
+ import path53 from "path";
11816
12130
  var DATA_URI_MAX_BYTES = 256 * 1024;
11817
12131
  var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
11818
12132
  function formatMb4(bytes) {
@@ -11820,10 +12134,10 @@ function formatMb4(bytes) {
11820
12134
  }
11821
12135
  async function resolveFileInput(value, client, logger) {
11822
12136
  if (isMediaRef(value)) return value;
11823
- const filePath = path52.resolve(value);
12137
+ const filePath = path53.resolve(value);
11824
12138
  let stat;
11825
12139
  try {
11826
- stat = fs45.statSync(filePath);
12140
+ stat = fs46.statSync(filePath);
11827
12141
  } catch {
11828
12142
  throw new ValidationError(`Input file not found: ${value} (pass a local path or an https URL)`);
11829
12143
  }
@@ -11834,9 +12148,9 @@ async function resolveFileInput(value, client, logger) {
11834
12148
  `Input file ${value} is ${formatMb4(stat.size)}; Replicate accepts uploads under 100MB. Host it and pass an https URL instead.`
11835
12149
  );
11836
12150
  }
11837
- const filename = path52.basename(filePath);
12151
+ const filename = path53.basename(filePath);
11838
12152
  const contentType = mimeFromFilename(filename);
11839
- const bytes = fs45.readFileSync(filePath);
12153
+ const bytes = fs46.readFileSync(filePath);
11840
12154
  if (stat.size <= DATA_URI_MAX_BYTES) {
11841
12155
  logger.debug(`Inlining ${filename} (${stat.size} bytes) as a data URI`);
11842
12156
  return `data:${contentType};base64,${bytes.toString("base64")}`;
@@ -11963,8 +12277,8 @@ function registerReplicateCommands(program2) {
11963
12277
  }
11964
12278
 
11965
12279
  // src/commands/check.ts
11966
- import fs46 from "fs";
11967
- import os4 from "os";
12280
+ import fs47 from "fs";
12281
+ import os5 from "os";
11968
12282
 
11969
12283
  // src/commands/check-helpers/binary-check.ts
11970
12284
  import { execa as execa2 } from "execa";
@@ -12212,8 +12526,8 @@ function registerCheckCommand(program2) {
12212
12526
  logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
12213
12527
  }
12214
12528
  if (!legacyClaudeEnvEnabled()) {
12215
- const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os4.homedir();
12216
- const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs46.existsSync(f));
12529
+ const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os5.homedir();
12530
+ const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs47.existsSync(f));
12217
12531
  if (legacy.length > 0) {
12218
12532
  logger.info(
12219
12533
  `Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
@@ -12330,8 +12644,8 @@ function registerCheckCommand(program2) {
12330
12644
  }
12331
12645
 
12332
12646
  // src/commands/doc/convert.ts
12333
- import fs47 from "fs";
12334
- import path53 from "path";
12647
+ import fs48 from "fs";
12648
+ import path54 from "path";
12335
12649
 
12336
12650
  // src/commands/doc/default-prompt.ts
12337
12651
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -12349,7 +12663,7 @@ Output only the markdown content without any preamble or explanation.`;
12349
12663
  // src/commands/doc/convert.ts
12350
12664
  function autoNameBase(input) {
12351
12665
  if (input === STDIN_ARG) return "stdin";
12352
- return path53.basename(input, path53.extname(input));
12666
+ return path54.basename(input, path54.extname(input));
12353
12667
  }
12354
12668
  async function convertDocuments(opts) {
12355
12669
  const { output, autoName = false, verbose, logger } = opts;
@@ -12366,13 +12680,13 @@ async function convertDocuments(opts) {
12366
12680
  try {
12367
12681
  let outputPath;
12368
12682
  if (output) {
12369
- outputPath = path53.resolve(output);
12683
+ outputPath = path54.resolve(output);
12370
12684
  } else if (autoName && opts.inputs.length === 1 && opts.inputs[0]) {
12371
- outputPath = path53.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
12685
+ outputPath = path54.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
12372
12686
  } else {
12373
- outputPath = path53.join(getOutputDir(), "document-extraction.md");
12687
+ outputPath = path54.join(getOutputDir(), "document-extraction.md");
12374
12688
  }
12375
- fs47.mkdirSync(path53.dirname(outputPath), { recursive: true });
12689
+ fs48.mkdirSync(path54.dirname(outputPath), { recursive: true });
12376
12690
  failed = await convertResolved(inputs, { outputPath, model, prompt, verbose, logger });
12377
12691
  } finally {
12378
12692
  cleanup();
@@ -12445,7 +12759,7 @@ async function convertResolved(inputs, ctx) {
12445
12759
  `);
12446
12760
  for (const r of results) {
12447
12761
  lines.push(`
12448
- ## ${path53.basename(r.file).replace(/[<>]/g, "\\$&")}
12762
+ ## ${path54.basename(r.file).replace(/[<>]/g, "\\$&")}
12449
12763
  `);
12450
12764
  if (r.status === "success" && r.markdown) {
12451
12765
  lines.push(`${r.markdown}
@@ -12458,7 +12772,7 @@ async function convertResolved(inputs, ctx) {
12458
12772
  }
12459
12773
  lines.push("\n---\n");
12460
12774
  }
12461
- fs47.writeFileSync(outputPath, lines.join("\n"), "utf8");
12775
+ fs48.writeFileSync(outputPath, lines.join("\n"), "utf8");
12462
12776
  const succeeded = results.filter((r) => r.status === "success").length;
12463
12777
  console.log(`
12464
12778
  ${"=".repeat(50)}`);
@@ -12512,12 +12826,12 @@ function registerDocCommands(program2) {
12512
12826
 
12513
12827
  // src/commands/media/index.ts
12514
12828
  init_errors();
12515
- import fs50 from "fs";
12516
- import path56 from "path";
12829
+ import fs51 from "fs";
12830
+ import path57 from "path";
12517
12831
 
12518
12832
  // src/commands/media/batch.ts
12519
- import fs48 from "fs";
12520
- import path54 from "path";
12833
+ import fs49 from "fs";
12834
+ import path55 from "path";
12521
12835
 
12522
12836
  // src/commands/media/detect-type.ts
12523
12837
  var VIDEO_EXTS2 = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -12713,10 +13027,10 @@ async function batchOptimize(opts) {
12713
13027
  verbose,
12714
13028
  logger
12715
13029
  } = opts;
12716
- fs48.mkdirSync(outputDir, { recursive: true });
12717
- const entries = fs48.readdirSync(inputDir);
13030
+ fs49.mkdirSync(outputDir, { recursive: true });
13031
+ const entries = fs49.readdirSync(inputDir);
12718
13032
  const mediaFiles = entries.filter((f) => {
12719
- const ext = path54.extname(f).toLowerCase();
13033
+ const ext = path55.extname(f).toLowerCase();
12720
13034
  return ALL_EXTS.has(ext);
12721
13035
  });
12722
13036
  if (mediaFiles.length === 0) {
@@ -12727,9 +13041,9 @@ async function batchOptimize(opts) {
12727
13041
  let succeeded = 0;
12728
13042
  const errors = [];
12729
13043
  for (const file of mediaFiles) {
12730
- const inputPath = path54.join(inputDir, file);
12731
- const outputPath = path54.join(outputDir, file);
12732
- const ext = path54.extname(file).toLowerCase();
13044
+ const inputPath = path55.join(inputDir, file);
13045
+ const outputPath = path55.join(outputDir, file);
13046
+ const ext = path55.extname(file).toLowerCase();
12733
13047
  const kind = extToKind(ext);
12734
13048
  logger?.debug(`Processing: ${file}`);
12735
13049
  try {
@@ -12766,8 +13080,8 @@ async function batchOptimize(opts) {
12766
13080
 
12767
13081
  // src/commands/media/split-video.ts
12768
13082
  init_errors();
12769
- import fs49 from "fs";
12770
- import path55 from "path";
13083
+ import fs50 from "fs";
13084
+ import path56 from "path";
12771
13085
  async function splitVideo(opts) {
12772
13086
  await assertFfmpeg();
12773
13087
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -12778,10 +13092,10 @@ async function splitVideo(opts) {
12778
13092
  logger?.info("Video is short enough \u2014 no splitting needed");
12779
13093
  return [input];
12780
13094
  }
12781
- fs49.mkdirSync(outputDir, { recursive: true });
12782
- const ext = path55.extname(input) || ".mp4";
12783
- const baseName = path55.basename(input, ext);
12784
- const outputPattern = path55.join(outputDir, `${baseName}_chunk_%03d${ext}`);
13095
+ fs50.mkdirSync(outputDir, { recursive: true });
13096
+ const ext = path56.extname(input) || ".mp4";
13097
+ const baseName = path56.basename(input, ext);
13098
+ const outputPattern = path56.join(outputDir, `${baseName}_chunk_%03d${ext}`);
12785
13099
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
12786
13100
  const args = [
12787
13101
  "-i",
@@ -12800,8 +13114,8 @@ async function splitVideo(opts) {
12800
13114
  await runFfmpeg(args, { verbose, logger });
12801
13115
  const files = [];
12802
13116
  for (let i = 0; i < numChunks + 1; i++) {
12803
- const fname = path55.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
12804
- if (fs49.existsSync(fname)) files.push(fname);
13117
+ const fname = path56.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
13118
+ if (fs50.existsSync(fname)) files.push(fname);
12805
13119
  }
12806
13120
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
12807
13121
  return files;
@@ -12815,9 +13129,9 @@ function registerMediaCommands(program2) {
12815
13129
  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(
12816
13130
  async (opts) => {
12817
13131
  const logger = createLogger({ verbose: opts.verbose ?? false });
12818
- if (!fs50.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
12819
- fs50.mkdirSync(path56.dirname(path56.resolve(opts.output)), { recursive: true });
12820
- const ext = path56.extname(opts.input).toLowerCase();
13132
+ if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
13133
+ fs51.mkdirSync(path57.dirname(path57.resolve(opts.output)), { recursive: true });
13134
+ const ext = path57.extname(opts.input).toLowerCase();
12821
13135
  const kind = extToKind(ext);
12822
13136
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
12823
13137
  const quality = Number.parseInt(opts.quality, 10);
@@ -12857,7 +13171,7 @@ function registerMediaCommands(program2) {
12857
13171
  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(
12858
13172
  async (opts) => {
12859
13173
  const logger = createLogger({ verbose: opts.verbose ?? false });
12860
- if (!fs50.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
13174
+ if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
12861
13175
  const chunks = await splitVideo({
12862
13176
  input: opts.input,
12863
13177
  outputDir: opts.outputDir,
@@ -12873,7 +13187,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
12873
13187
  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(
12874
13188
  async (opts) => {
12875
13189
  const logger = createLogger({ verbose: opts.verbose ?? false });
12876
- if (!fs50.existsSync(opts.inputDir))
13190
+ if (!fs51.existsSync(opts.inputDir))
12877
13191
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
12878
13192
  const result = await batchOptimize({
12879
13193
  inputDir: opts.inputDir,