@mrgoonie/multix 1.0.2 → 1.0.4

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
@@ -58,8 +58,8 @@ var init_errors = __esm({
58
58
  code;
59
59
  };
60
60
  ConfigError = class extends MultixError {
61
- constructor(message) {
62
- super(message, "CONFIG_ERROR");
61
+ constructor(message, options) {
62
+ super(message, "CONFIG_ERROR", options);
63
63
  this.name = "ConfigError";
64
64
  }
65
65
  };
@@ -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 {
@@ -639,7 +768,11 @@ var PollTimeoutError = class extends MultixError {
639
768
  function isTransientHttpError(error) {
640
769
  if (!(error instanceof HttpError)) return false;
641
770
  const { status } = error;
642
- return status === 0 || status === 408 || status === 429 || status >= 500;
771
+ return status === 408 || status === 429 || isRetryableServerStatus(status);
772
+ }
773
+ function isRetryableServerStatus(status) {
774
+ if (status === 501 || status === 505) return false;
775
+ return status === 0 || status >= 500;
643
776
  }
644
777
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
645
778
  async function pollUntil(opts) {
@@ -688,6 +821,97 @@ async function pollUntil(opts) {
688
821
  // src/providers/atlascloud/client.ts
689
822
  init_errors();
690
823
 
824
+ // src/core/fetch-error.ts
825
+ init_errors();
826
+ init_redact();
827
+ var MAX_CAUSE_DEPTH = 4;
828
+ var TLS_CODES = /* @__PURE__ */ new Set([
829
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
830
+ "SELF_SIGNED_CERT_IN_CHAIN",
831
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
832
+ "ERR_TLS_CERT_ALTNAME_INVALID"
833
+ ]);
834
+ function kindOfCode(code) {
835
+ if (typeof code !== "string") return void 0;
836
+ if (code === "ERR_INVALID_URL") return "url";
837
+ if (code === "ERR_INVALID_CHAR" || code === "ERR_INVALID_HTTP_TOKEN") return "header";
838
+ if (TLS_CODES.has(code) || code.startsWith("CERT_") || code.startsWith("UNABLE_TO_")) {
839
+ return "tls";
840
+ }
841
+ if (code === "UND_ERR_INVALID_ARG" || code === "UND_ERR_NOT_SUPPORTED") return "request";
842
+ return void 0;
843
+ }
844
+ function kindOfMessage(message) {
845
+ if (typeof message !== "string") return void 0;
846
+ if (/Invalid URL|Failed to parse URL/i.test(message)) return "url";
847
+ if (/ByteString|is not a legal HTTP header value|invalid header value|invalid header name/i.test(
848
+ message
849
+ )) {
850
+ return "header";
851
+ }
852
+ if (/duplex option is required|cannot have body|unexpected redirect/i.test(message)) {
853
+ return "request";
854
+ }
855
+ return void 0;
856
+ }
857
+ function causeChain(err2) {
858
+ const out = [];
859
+ let current = err2;
860
+ for (let depth = 0; depth <= MAX_CAUSE_DEPTH && current && typeof current === "object"; depth++) {
861
+ const e = current;
862
+ out.push(e);
863
+ if (Array.isArray(e.errors)) {
864
+ for (const member of e.errors) if (member && typeof member === "object") out.push(member);
865
+ }
866
+ current = e.cause;
867
+ }
868
+ return out;
869
+ }
870
+ function deterministicKind(err2) {
871
+ for (const e of causeChain(err2)) {
872
+ const kind = kindOfCode(e.code) ?? kindOfMessage(e.message);
873
+ if (kind) return kind;
874
+ }
875
+ return void 0;
876
+ }
877
+ function isNetworkFetchFailure(err2) {
878
+ return deterministicKind(err2) === void 0;
879
+ }
880
+ function scrub(message) {
881
+ return message.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi, (u) => redactUrl(u)).replace(/[?#](?!\[redacted\])[^\s"'<>]*=[^\s"'<>]*/g, "?[redacted]");
882
+ }
883
+ function messageOf(e) {
884
+ return typeof e?.message === "string" ? scrub(e.message) : "";
885
+ }
886
+ function detailOf(err2) {
887
+ const chain = causeChain(err2);
888
+ const parts = [messageOf(chain[0]), messageOf(chain[1])].filter(Boolean);
889
+ return parts.length > 0 ? parts.join(": ") : scrub(String(err2));
890
+ }
891
+ function toFetchError(err2, url, opts = {}) {
892
+ const options = { cause: err2 };
893
+ const prefix = opts.prefix ?? "";
894
+ const target = opts.target ?? redactUrl(url);
895
+ switch (deterministicKind(err2)) {
896
+ case "url":
897
+ return new ConfigError(`${prefix}Invalid request URL: ${target}`, options);
898
+ case "header":
899
+ return new ConfigError(
900
+ `${prefix}Invalid HTTP header value in the request to ${target}. Check the API key and other header values for non-ASCII characters such as smart quotes, or line breaks.`,
901
+ options
902
+ );
903
+ case "tls":
904
+ case "request":
905
+ return new ProviderError(
906
+ `${prefix}Request to ${target} failed: ${detailOf(err2)}`,
907
+ opts.provider ?? "http",
908
+ options
909
+ );
910
+ default:
911
+ return new HttpError(0, `${prefix}${detailOf(err2)}`, url, void 0, options);
912
+ }
913
+ }
914
+
691
915
  // src/core/fetch-json.ts
692
916
  init_errors();
693
917
 
@@ -715,8 +939,7 @@ async function fetchFollowingRedirects(url, init = {}, opts = {}) {
715
939
  response = await doFetch(current, hopInit);
716
940
  } catch (cause) {
717
941
  if (!opts.wrapNetworkErrors) throw cause;
718
- const msg = cause instanceof Error ? cause.message : String(cause);
719
- throw new HttpError(0, msg, current);
942
+ throw toFetchError(cause, current);
720
943
  }
721
944
  if (validateUrl && response.url && response.url !== current) {
722
945
  try {
@@ -784,8 +1007,7 @@ async function fetchWithValidatedRedirects(url, opts = {}) {
784
1007
  try {
785
1008
  return { response: await doFetch(url, init), url };
786
1009
  } catch (cause) {
787
- const msg = cause instanceof Error ? cause.message : String(cause);
788
- throw new HttpError(0, msg, url);
1010
+ throw toFetchError(cause, url);
789
1011
  }
790
1012
  }
791
1013
  return fetchFollowingRedirects(url, init, {
@@ -868,6 +1090,7 @@ async function fetchJson(opts) {
868
1090
  }
869
1091
 
870
1092
  // src/providers/atlascloud/prediction-output.ts
1093
+ init_errors();
871
1094
  var SUCCESS_STATUSES = /* @__PURE__ */ new Set(["completed", "succeeded", "success"]);
872
1095
  var FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "canceled", "cancelled", "timeout"]);
873
1096
  function norm(status) {
@@ -917,6 +1140,9 @@ function collectOutputs(prediction) {
917
1140
  }
918
1141
  return out;
919
1142
  }
1143
+ function checkOutputUrl(url) {
1144
+ checkUrlSafety(url, { provider: ATLAS_PROVIDER });
1145
+ }
920
1146
  function isMediaOutput(value) {
921
1147
  const s = value.trim();
922
1148
  return /^https?:\/\//i.test(s) || /^data:[^,]*,/i.test(s);
@@ -941,6 +1167,7 @@ var EXT_CATEGORY = {
941
1167
  flac: "audio",
942
1168
  ogg: "audio",
943
1169
  opus: "audio",
1170
+ weba: "audio",
944
1171
  pcm: "audio",
945
1172
  glb: "3d",
946
1173
  gltf: "3d",
@@ -958,8 +1185,14 @@ var MIME_EXT = {
958
1185
  "image/jpeg": "jpg",
959
1186
  "image/webp": "webp",
960
1187
  "image/gif": "gif",
1188
+ "image/avif": "avif",
1189
+ "image/bmp": "bmp",
1190
+ "image/svg+xml": "svg",
961
1191
  "video/mp4": "mp4",
962
1192
  "video/webm": "webm",
1193
+ "video/x-matroska": "mkv",
1194
+ "video/matroska": "mkv",
1195
+ "audio/webm": "weba",
963
1196
  "audio/mpeg": "mp3",
964
1197
  "audio/mp3": "mp3",
965
1198
  "audio/wav": "wav",
@@ -976,6 +1209,7 @@ function extensionForContentType(contentType) {
976
1209
  const mime = contentType?.split(";")[0]?.trim().toLowerCase();
977
1210
  return mime ? MIME_EXT[mime] : void 0;
978
1211
  }
1212
+ var KNOWN_EXTENSIONS = /* @__PURE__ */ new Set([...Object.keys(EXT_CATEGORY), "svg"]);
979
1213
  function outputExtension(value) {
980
1214
  const s = value.trim();
981
1215
  const dataMatch = /^data:([^;,]+)/i.exec(s);
@@ -989,6 +1223,7 @@ function outputExtension(value) {
989
1223
  const m = /\.([a-z0-9]{2,5})$/i.exec(pathname);
990
1224
  if (!m?.[1]) return void 0;
991
1225
  const ext = m[1].toLowerCase();
1226
+ if (!KNOWN_EXTENSIONS.has(ext)) return void 0;
992
1227
  return ext === "jpeg" ? "jpg" : ext;
993
1228
  }
994
1229
  function outputCategory(value) {
@@ -1022,13 +1257,32 @@ function classifyOutputs(prediction) {
1022
1257
  function hasNsfwFlag(prediction) {
1023
1258
  return Array.isArray(prediction.has_nsfw_contents) && prediction.has_nsfw_contents.some(Boolean);
1024
1259
  }
1260
+ function thumbnailSource(prediction) {
1261
+ return prediction.outputs ?? prediction.output ?? null;
1262
+ }
1263
+ var MAX_PREDICTION_ID_LENGTH = 128;
1264
+ var NAME_ID_LENGTH = 64;
1265
+ function fnv1a(value) {
1266
+ let hash = 2166136261;
1267
+ for (let i = 0; i < value.length; i++) {
1268
+ hash ^= value.charCodeAt(i);
1269
+ hash = Math.imul(hash, 16777619) >>> 0;
1270
+ }
1271
+ return hash.toString(16).padStart(8, "0");
1272
+ }
1025
1273
  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}`;
1274
+ if (predictionId.length === 0 || predictionId.length > MAX_PREDICTION_ID_LENGTH) {
1275
+ throw new ProviderError(
1276
+ `Atlas Cloud returned an unusable prediction id (${predictionId.length === 0 ? "empty" : `${predictionId.length} characters`}).`,
1277
+ ATLAS_PROVIDER
1278
+ );
1279
+ }
1280
+ const short = predictionId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, NAME_ID_LENGTH);
1281
+ const id = short === predictionId ? short : short ? `${short}-${fnv1a(predictionId)}` : fnv1a(predictionId);
1282
+ return `atlas-${id}-${String(index + 1).padStart(2, "0")}.${ext}`;
1028
1283
  }
1029
1284
 
1030
1285
  // src/providers/atlascloud/client.ts
1031
- var ATLAS_PROVIDER = "atlascloud";
1032
1286
  var DEFAULT_UPLOAD_TIMEOUT_MS = 3e5;
1033
1287
  var CATALOG_TIMEOUT_MS = 15e3;
1034
1288
  var PREDICTION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
@@ -1122,13 +1376,13 @@ var AtlasCloudClient = class {
1122
1376
  }
1123
1377
  /** Submit a generation job. `body` holds `model` plus the model's own fields, flattened. */
1124
1378
  async submit(kind, body) {
1125
- const path57 = SUBMIT_PATHS[kind];
1126
- if (!path57) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
1379
+ const path58 = SUBMIT_PATHS[kind];
1380
+ if (!path58) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
1127
1381
  if (typeof body.model !== "string" || !body.model.trim()) {
1128
1382
  throw new ValidationError("Atlas Cloud request body needs a non-empty `model`");
1129
1383
  }
1130
1384
  const env = await fetchJson({
1131
- url: `${this.baseUrl}${path57}`,
1385
+ url: `${this.baseUrl}${path58}`,
1132
1386
  method: "POST",
1133
1387
  headers: this.authHeaders(),
1134
1388
  body,
@@ -1204,7 +1458,7 @@ var AtlasCloudClient = class {
1204
1458
  );
1205
1459
  } catch (cause) {
1206
1460
  if (cause instanceof HttpError) throw cause;
1207
- throw new HttpError(0, cause instanceof Error ? cause.message : String(cause), url);
1461
+ throw toFetchError(cause, url, { provider: "atlascloud" });
1208
1462
  }
1209
1463
  if (!response.ok) throw await httpErrorFromResponse(response, url);
1210
1464
  const json = await response.json();
@@ -1316,7 +1570,7 @@ function requireAtlasKey() {
1316
1570
  }
1317
1571
  function createAtlasClientFromEnv() {
1318
1572
  const apiKey = requireAtlasKey();
1319
- const baseUrl = resolveProviderBaseUrl(ATLAS_ROUTING);
1573
+ const baseUrl = checkBaseUrl(resolveProviderBaseUrl(ATLAS_ROUTING), "ATLASCLOUD_BASE_URL");
1320
1574
  return new AtlasCloudClient({ apiKey, baseUrl });
1321
1575
  }
1322
1576
  async function resolveMediaRef(client, ref, logger) {
@@ -1386,8 +1640,8 @@ async function resolveMediaRefs(client, refs, logger) {
1386
1640
 
1387
1641
  // src/providers/atlascloud/commands/prediction-runner.ts
1388
1642
  init_errors();
1389
- import fs8 from "fs";
1390
- import path7 from "path";
1643
+ import fs9 from "fs";
1644
+ import path8 from "path";
1391
1645
 
1392
1646
  // src/core/http-client.ts
1393
1647
  import fs3 from "fs";
@@ -1527,8 +1781,7 @@ async function fetchBytes(urlOrDataUri) {
1527
1781
  signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
1528
1782
  });
1529
1783
  } catch (cause) {
1530
- const msg = cause instanceof Error ? cause.message : String(cause);
1531
- throw new HttpError(0, msg, urlOrDataUri);
1784
+ throw toFetchError(cause, urlOrDataUri);
1532
1785
  }
1533
1786
  if (!response.ok) {
1534
1787
  throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
@@ -1787,14 +2040,18 @@ function detectThumbUrl(obj) {
1787
2040
  }
1788
2041
  return null;
1789
2042
  }
2043
+ function resultDest(result) {
2044
+ const dest = result?.dest;
2045
+ return typeof dest === "string" ? dest : void 0;
2046
+ }
1790
2047
  async function downloadThumbBeside(thumbUrl, videoPath, logger, download = downloadFile) {
1791
2048
  const match = thumbUrl.match(IMAGE_EXT_RE);
1792
2049
  const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
1793
2050
  const dir = path6.dirname(videoPath);
1794
2051
  const base = path6.basename(videoPath, path6.extname(videoPath));
1795
- const dest = path6.join(dir, `${base}_thumb.${ext}`);
2052
+ const planned = path6.join(dir, `${base}_thumb.${ext}`);
1796
2053
  try {
1797
- await download(thumbUrl, dest);
2054
+ const dest = resultDest(await download(thumbUrl, planned)) ?? planned;
1798
2055
  logger?.success(`Saved thumb ${dest}`);
1799
2056
  return dest;
1800
2057
  } catch (e) {
@@ -1822,6 +2079,189 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
1822
2079
  return saved;
1823
2080
  }
1824
2081
 
2082
+ // src/providers/atlascloud/commands/output-claims.ts
2083
+ init_errors();
2084
+ import fs8 from "fs";
2085
+ import os2 from "os";
2086
+ import path7 from "path";
2087
+ var PART = ".part";
2088
+ var PART_SUFFIX = /(?:\.\d+-\d+-[a-z0-9]+)?\.part$/i;
2089
+ var TEMP_TAIL = /^\.(\d+)-\d+-[a-z0-9]+\.part$/i;
2090
+ var MAX_UNIQUE_ATTEMPTS = 1e3;
2091
+ var UNREADABLE_LOCK_GRACE_MS = 1e4;
2092
+ var foldCase = process.platform === "win32" || process.platform === "darwin";
2093
+ var fold = (s) => foldCase ? s.toLowerCase() : s;
2094
+ function stemOf(name) {
2095
+ const base = name.replace(PART_SUFFIX, "");
2096
+ return fold(base.slice(0, base.length - path7.extname(base).length));
2097
+ }
2098
+ function listDir(dir) {
2099
+ try {
2100
+ return fs8.readdirSync(dir);
2101
+ } catch {
2102
+ return [];
2103
+ }
2104
+ }
2105
+ function removeQuietly2(file) {
2106
+ try {
2107
+ fs8.rmSync(file, { force: true });
2108
+ } catch {
2109
+ }
2110
+ }
2111
+ function savedOutput(file) {
2112
+ const dir = path7.dirname(file);
2113
+ const stem = stemOf(path7.basename(file));
2114
+ const matches = listDir(dir).filter((n) => {
2115
+ if (PART_SUFFIX.test(n) || stemOf(n) !== stem) return false;
2116
+ const st = fs8.statSync(path7.join(dir, n), { throwIfNoEntry: false });
2117
+ return st?.isFile() === true && st.size > 0;
2118
+ });
2119
+ const exact = matches.find((n) => n === path7.basename(file));
2120
+ const found = exact ?? matches.sort()[0];
2121
+ return found === void 0 ? void 0 : path7.join(dir, found);
2122
+ }
2123
+ function* candidateNames(file) {
2124
+ const dir = path7.dirname(file);
2125
+ const ext = path7.extname(file);
2126
+ const stem = path7.basename(file, ext);
2127
+ const taken = new Set(listDir(dir).map(stemOf));
2128
+ for (let n = 0; n < MAX_UNIQUE_ATTEMPTS; n++) {
2129
+ const name = n === 0 ? stem : `${stem}-${n}`;
2130
+ if (!taken.has(fold(name))) yield path7.join(dir, `${name}${ext}`);
2131
+ }
2132
+ }
2133
+ function readLock(lock) {
2134
+ try {
2135
+ const v = JSON.parse(fs8.readFileSync(lock, "utf8"));
2136
+ if (Number.isInteger(v.pid) && v.pid > 0 && typeof v.host === "string") {
2137
+ return v;
2138
+ }
2139
+ } catch {
2140
+ }
2141
+ return void 0;
2142
+ }
2143
+ function isProcessAlive(pid) {
2144
+ if (pid === process.pid) return true;
2145
+ try {
2146
+ process.kill(pid, 0);
2147
+ return true;
2148
+ } catch (e) {
2149
+ return e.code !== "ESRCH";
2150
+ }
2151
+ }
2152
+ function isStaleLock(lock) {
2153
+ const info = readLock(lock);
2154
+ if (!info) {
2155
+ const st = fs8.statSync(lock, { throwIfNoEntry: false });
2156
+ return st === void 0 || Date.now() - st.mtimeMs > UNREADABLE_LOCK_GRACE_MS;
2157
+ }
2158
+ if (info.host !== os2.hostname()) return false;
2159
+ return !isProcessAlive(info.pid);
2160
+ }
2161
+ var activeClaims = /* @__PURE__ */ new Set();
2162
+ function tryLock(dest) {
2163
+ const lock = `${dest}${PART}`;
2164
+ const content = JSON.stringify({
2165
+ pid: process.pid,
2166
+ host: os2.hostname(),
2167
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2168
+ });
2169
+ for (let attempt = 0; attempt < 2; attempt++) {
2170
+ try {
2171
+ fs8.writeFileSync(lock, content, { flag: "wx" });
2172
+ activeClaims.add(dest);
2173
+ installInterruptCleanup();
2174
+ return true;
2175
+ } catch (e) {
2176
+ if (e.code !== "EEXIST") throw e;
2177
+ if (attempt > 0 || !isStaleLock(lock)) return false;
2178
+ removeQuietly2(lock);
2179
+ }
2180
+ }
2181
+ return false;
2182
+ }
2183
+ function removeOrphanedTemps(dest) {
2184
+ const base = path7.basename(dest);
2185
+ const dir = path7.dirname(dest);
2186
+ for (const n of listDir(dir)) {
2187
+ if (!n.startsWith(base)) continue;
2188
+ const m = TEMP_TAIL.exec(n.slice(base.length));
2189
+ if (m?.[1] && !isProcessAlive(Number(m[1]))) removeQuietly2(path7.join(dir, n));
2190
+ }
2191
+ }
2192
+ function releaseClaim(dest) {
2193
+ if (!activeClaims.delete(dest)) return;
2194
+ removeQuietly2(`${dest}${PART}`);
2195
+ }
2196
+ function cleanupActiveClaims() {
2197
+ for (const dest of [...activeClaims]) {
2198
+ const base = path7.basename(dest);
2199
+ const dir = path7.dirname(dest);
2200
+ for (const n of listDir(dir)) {
2201
+ if (!n.startsWith(base)) continue;
2202
+ const m = TEMP_TAIL.exec(n.slice(base.length));
2203
+ if (m?.[1] && Number(m[1]) === process.pid) removeQuietly2(path7.join(dir, n));
2204
+ }
2205
+ releaseClaim(dest);
2206
+ }
2207
+ }
2208
+ var INTERRUPT_EXIT_CODES = { SIGINT: 130, SIGTERM: 143 };
2209
+ function handleInterrupt(signal, exit = (code) => process.exit(code)) {
2210
+ cleanupActiveClaims();
2211
+ exit(INTERRUPT_EXIT_CODES[signal]);
2212
+ }
2213
+ var interruptCleanupInstalled = false;
2214
+ function installInterruptCleanup() {
2215
+ if (interruptCleanupInstalled) return;
2216
+ interruptCleanupInstalled = true;
2217
+ process.once("SIGINT", () => handleInterrupt("SIGINT"));
2218
+ process.once("SIGTERM", () => handleInterrupt("SIGTERM"));
2219
+ }
2220
+ function heldMessage(dest) {
2221
+ const info = readLock(`${dest}${PART}`);
2222
+ const holder = info ? ` (pid ${info.pid} on ${info.host})` : "";
2223
+ const advice = "Wait for it to finish, then retry. A lock left by a run that has stopped is replaced automatically.";
2224
+ return `${dest}${PART} is held by another multix run${holder} downloading this output. ${advice}`;
2225
+ }
2226
+ function claimOutputPath(file, policy) {
2227
+ if (policy === "overwrite") return file;
2228
+ fs8.mkdirSync(path7.dirname(file), { recursive: true });
2229
+ if (policy === "reuse") {
2230
+ if (!tryLock(file)) throw new ProviderError(heldMessage(file), ATLAS_PROVIDER);
2231
+ removeOrphanedTemps(file);
2232
+ return file;
2233
+ }
2234
+ for (const candidate of candidateNames(file)) {
2235
+ if (!tryLock(candidate)) continue;
2236
+ if (fs8.existsSync(candidate)) {
2237
+ releaseClaim(candidate);
2238
+ continue;
2239
+ }
2240
+ removeOrphanedTemps(candidate);
2241
+ return candidate;
2242
+ }
2243
+ throw new ProviderError(
2244
+ `No free file name for ${file} after ${MAX_UNIQUE_ATTEMPTS} attempts; choose another --output.`,
2245
+ ATLAS_PROVIDER
2246
+ );
2247
+ }
2248
+ async function downloadClaimed(url, choose, policy, opts) {
2249
+ let claimed;
2250
+ try {
2251
+ const { dest } = await downloadToFile(url, {
2252
+ ...opts,
2253
+ dest: (response) => {
2254
+ const dest2 = claimOutputPath(choose(response), policy);
2255
+ if (policy !== "overwrite") claimed = dest2;
2256
+ return dest2;
2257
+ }
2258
+ });
2259
+ return dest;
2260
+ } finally {
2261
+ if (claimed) releaseClaim(claimed);
2262
+ }
2263
+ }
2264
+
1825
2265
  // src/providers/atlascloud/commands/prediction-runner.ts
1826
2266
  function addRunOptions(cmd, cfg) {
1827
2267
  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 +2322,13 @@ function assertEndpoint(spec, expected, model) {
1882
2322
  function capitalize(s) {
1883
2323
  return s.charAt(0).toUpperCase() + s.slice(1);
1884
2324
  }
1885
- function resumeCommand(id) {
1886
- return `multix atlas status ${id} --wait --download`;
2325
+ function resumeCommand(id, output) {
2326
+ const base = `multix atlas status ${id} --wait --download`;
2327
+ return output ? `${base} --output ${shellQuote(output)}` : base;
2328
+ }
2329
+ function shellQuote(value) {
2330
+ if (/^[\w@%+=:,./\\-]+$/.test(value)) return value;
2331
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
1887
2332
  }
1888
2333
  async function executePrediction(o) {
1889
2334
  const { client, logger, opts } = o;
@@ -1893,22 +2338,27 @@ async function executePrediction(o) {
1893
2338
  const id = submitted.id;
1894
2339
  if (opts.async) {
1895
2340
  console.log(id);
1896
- console.error(`Prediction submitted. Resume with: ${resumeCommand(id)}`);
2341
+ console.error(`Prediction submitted. Resume with: ${resumeCommand(id, opts.output)}`);
1897
2342
  return { predictionId: id, files: [], texts: [] };
1898
2343
  }
1899
2344
  logger.info(`Prediction ${id} \u2014 waiting up to ${Math.round(timeoutMs / 1e3)}s...`);
1900
2345
  const final = isTerminalStatus(submitted.status) ? submitted : await client.waitForPrediction(id, {
1901
2346
  timeoutMs,
1902
2347
  intervalMs: o.pollIntervalMs,
1903
- resumeHint: `Resume with: ${resumeCommand(id)}`,
2348
+ resumeHint: `Resume with: ${resumeCommand(id, opts.output)}`,
1904
2349
  onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
1905
2350
  });
1906
2351
  assertPredictionSucceeded(final);
1907
- const delivered = await deliverPrediction(final, { logger, opts, hint: o.hint });
2352
+ const delivered = await deliverPrediction(final, {
2353
+ logger,
2354
+ opts,
2355
+ hint: o.hint,
2356
+ predictionId: id
2357
+ });
1908
2358
  return { predictionId: id, prediction: final, ...delivered };
1909
2359
  }
1910
2360
  function isDirectoryOutput(output) {
1911
- return /[\\/]$/.test(output) || path7.extname(output) === "" || fs8.existsSync(output) && fs8.statSync(output).isDirectory();
2361
+ return /[\\/]$/.test(output) || path8.extname(output) === "" || fs9.existsSync(output) && fs9.statSync(output).isDirectory();
1912
2362
  }
1913
2363
  function planOutputPaths(urls, cfg) {
1914
2364
  const exts = urls.map((u) => {
@@ -1917,32 +2367,34 @@ function planOutputPaths(urls, cfg) {
1917
2367
  });
1918
2368
  const output = cfg.output;
1919
2369
  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)));
2370
+ const dir = output ? path8.resolve(output) : cfg.outDir();
2371
+ return exts.map((ext, i) => path8.join(dir, outputFileName(cfg.predictionId, i, ext)));
1922
2372
  }
1923
- const abs = path7.resolve(output);
2373
+ const abs = path8.resolve(output);
1924
2374
  if (urls.length === 1) return [abs];
1925
- const base = abs.slice(0, -path7.extname(abs).length);
2375
+ const base = abs.slice(0, -path8.extname(abs).length);
1926
2376
  return exts.map((ext, i) => `${base}-${String(i + 1).padStart(2, "0")}.${ext}`);
1927
2377
  }
1928
2378
  async function deliverPrediction(prediction, cfg) {
1929
2379
  const { logger, opts } = cfg;
2380
+ const id = cfg.predictionId ?? prediction.id;
2381
+ const retry = resumeCommand(id, opts.output);
1930
2382
  if (prediction.price !== void 0 && prediction.price !== "") {
1931
2383
  logger.info(`Cost: $${prediction.price}`);
1932
2384
  }
1933
2385
  const classified = classifyOutputs(prediction);
1934
2386
  if (classified.mode === "empty") {
1935
2387
  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.`,
2388
+ 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
2389
  ATLAS_PROVIDER
1938
2390
  );
1939
2391
  }
1940
2392
  for (const text of classified.texts) console.log(text);
1941
2393
  if (classified.mode === "text") {
1942
2394
  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")}
2395
+ const dest = isDirectoryOutput(opts.output) ? path8.resolve(opts.output, outputFileName(id, 0, "txt")) : path8.resolve(opts.output);
2396
+ fs9.mkdirSync(path8.dirname(dest), { recursive: true });
2397
+ fs9.writeFileSync(dest, `${classified.texts.join("\n\n")}
1946
2398
  `, "utf8");
1947
2399
  logger.success(`Saved text output to ${dest}`);
1948
2400
  return { files: [dest], texts: classified.texts };
@@ -1954,26 +2406,39 @@ async function deliverPrediction(prediction, cfg) {
1954
2406
  return { files: [], texts: classified.texts };
1955
2407
  }
1956
2408
  const dests = planOutputPaths(classified.urls, {
1957
- predictionId: prediction.id,
2409
+ predictionId: id,
1958
2410
  outDir: getOutputDir,
1959
2411
  output: opts.output,
1960
2412
  hint: cfg.hint
1961
2413
  });
1962
- const explicitFile = classified.urls.length === 1 && opts.output !== void 0 && !isDirectoryOutput(opts.output);
2414
+ const policy = namingPolicy(opts.output, classified.urls.length);
2415
+ const maxBytes = maxDownloadBytes();
1963
2416
  const saved = [];
2417
+ const downloaded = [];
2418
+ const reused = /* @__PURE__ */ new Set();
1964
2419
  let failed = 0;
1965
2420
  for (let i = 0; i < classified.urls.length; i++) {
1966
2421
  const url = classified.urls[i];
1967
2422
  const planned = dests[i];
1968
- const part = `${planned}.part`;
1969
2423
  try {
1970
- const contentType = await downloadFile(url, part);
1971
- const dest = explicitFile || outputExtension(url) ? planned : withContentTypeExtension(planned, contentType);
1972
- fs8.renameSync(part, dest);
2424
+ const existing = policy === "reuse" ? savedOutput(planned) : void 0;
2425
+ if (existing) {
2426
+ saved.push(existing);
2427
+ reused.add(existing);
2428
+ logger.info(`Already saved: ${existing}`);
2429
+ continue;
2430
+ }
2431
+ const dest = await downloadClaimed(
2432
+ url,
2433
+ (response) => policy === "overwrite" || outputExtension(url) ? planned : withContentTypeExtension(planned, response.headers.get("content-type")),
2434
+ policy,
2435
+ // Inline data: outputs are decoded by fetch, never sent over the network.
2436
+ { maxBytes, validateUrl: isDataUrl(url) ? void 0 : checkOutputUrl }
2437
+ );
1973
2438
  saved.push(dest);
2439
+ downloaded.push(dest);
1974
2440
  logger.debug(`Downloaded ${redactUrl(url)} -> ${dest}`);
1975
2441
  } catch (e) {
1976
- fs8.rmSync(part, { force: true });
1977
2442
  failed++;
1978
2443
  logger.warn(
1979
2444
  `Download failed (${redactUrl(url)}): ${e instanceof Error ? e.message : String(e)}`
@@ -1982,11 +2447,11 @@ async function deliverPrediction(prediction, cfg) {
1982
2447
  }
1983
2448
  if (saved.length === 0) {
1984
2449
  throw new ProviderError(
1985
- `Prediction ${prediction.id} produced ${classified.urls.length} output(s) but none downloaded. Retry with: ${resumeCommand(prediction.id)}`,
2450
+ `Prediction ${id} produced ${classified.urls.length} output(s) but none downloaded. Retry with: ${retry}`,
1986
2451
  ATLAS_PROVIDER
1987
2452
  );
1988
2453
  }
1989
- const images = saved.filter((f) => outputCategory(f) === "image");
2454
+ const images = downloaded.filter((f) => outputCategory(f) === "image");
1990
2455
  const fileOutput = opts.output && !isDirectoryOutput(opts.output) ? opts.output : void 0;
1991
2456
  const finalImages = finalizeGeneratedImages(images, {
1992
2457
  imageFormat: opts.imageFormat,
@@ -1997,23 +2462,42 @@ async function deliverPrediction(prediction, cfg) {
1997
2462
  const files = saved.map((f) => finalImages[images.indexOf(f)] ?? f);
1998
2463
  const firstVideo = files.find((f) => outputCategory(f) === "video");
1999
2464
  if (firstVideo) {
2000
- await maybeDownloadThumb(prediction, firstVideo, { skip: opts.thumb === false, logger });
2465
+ await maybeDownloadThumb(thumbnailSource(prediction), firstVideo, {
2466
+ skip: opts.thumb === false,
2467
+ logger,
2468
+ download: async (thumbUrl, planned) => {
2469
+ const existing = policy === "reuse" ? savedOutput(planned) : void 0;
2470
+ if (existing) return { dest: existing };
2471
+ const dest = await downloadClaimed(thumbUrl, () => planned, policy, {
2472
+ maxBytes,
2473
+ validateUrl: checkOutputUrl
2474
+ });
2475
+ return { dest };
2476
+ }
2477
+ });
2001
2478
  }
2002
2479
  console.log(`
2003
- Prediction ${prediction.id}: saved ${files.length} file(s)`);
2004
- for (const f of files) console.log(` ${f}`);
2480
+ Prediction ${id}: saved ${files.length} file(s)`);
2481
+ for (const f of files) console.log(` ${f}${reused.has(f) ? " (already saved)" : ""}`);
2005
2482
  if (failed > 0) {
2006
2483
  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)}`,
2484
+ `Prediction ${id}: downloaded ${saved.length} of ${classified.urls.length} output(s); ${failed} failed. Saved: ${files.join(", ")}. Retry with: ${retry}`,
2008
2485
  ATLAS_PROVIDER
2009
2486
  );
2010
2487
  }
2011
2488
  return { files, texts: classified.texts };
2012
2489
  }
2490
+ function isDataUrl(url) {
2491
+ return /^data:/i.test(url.trim());
2492
+ }
2493
+ function namingPolicy(output, outputCount) {
2494
+ if (!output || isDirectoryOutput(output)) return "reuse";
2495
+ return outputCount === 1 ? "overwrite" : "unique";
2496
+ }
2013
2497
  function withContentTypeExtension(planned, contentType) {
2014
2498
  const ext = extensionForContentType(contentType);
2015
2499
  if (!ext) return planned;
2016
- const current = path7.extname(planned);
2500
+ const current = path8.extname(planned);
2017
2501
  return `${current ? planned.slice(0, -current.length) : planned}.${ext}`;
2018
2502
  }
2019
2503
 
@@ -2148,12 +2632,12 @@ init_errors();
2148
2632
 
2149
2633
  // src/core/json-input.ts
2150
2634
  init_errors();
2151
- import fs9 from "fs";
2635
+ import fs10 from "fs";
2152
2636
  function parseJsonInput(raw, flag = "--input") {
2153
2637
  let text;
2154
2638
  if (raw.startsWith("@")) {
2155
2639
  try {
2156
- text = fs9.readFileSync(raw.slice(1), "utf8");
2640
+ text = fs10.readFileSync(raw.slice(1), "utf8");
2157
2641
  } catch (e) {
2158
2642
  const msg = e instanceof Error ? e.message : String(e);
2159
2643
  throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
@@ -2238,7 +2722,7 @@ function registerAtlasStatusCommand(parent) {
2238
2722
  const wait = Boolean(opts.wait || opts.download);
2239
2723
  const prediction = wait ? await client.waitForPrediction(predictionId, {
2240
2724
  timeoutMs: parseTimeoutMs(opts.waitTimeout, STATUS_WAIT_MS),
2241
- resumeHint: `Resume with: ${resumeCommand(predictionId)}`,
2725
+ resumeHint: `Resume with: ${resumeCommand(predictionId, opts.output)}`,
2242
2726
  onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
2243
2727
  }) : await client.getPrediction(predictionId);
2244
2728
  if (!opts.download) {
@@ -2247,7 +2731,11 @@ function registerAtlasStatusCommand(parent) {
2247
2731
  return;
2248
2732
  }
2249
2733
  assertPredictionSucceeded(prediction);
2250
- await deliverPrediction(prediction, { logger, opts: { ...opts, download: true } });
2734
+ await deliverPrediction(prediction, {
2735
+ logger,
2736
+ opts: { ...opts, download: true },
2737
+ predictionId
2738
+ });
2251
2739
  });
2252
2740
  }
2253
2741
 
@@ -2377,11 +2865,11 @@ function registerAtlasCloudCommands(program2) {
2377
2865
  }
2378
2866
 
2379
2867
  // src/providers/byteplus/commands/generate-3d.ts
2380
- import path9 from "path";
2868
+ import path10 from "path";
2381
2869
 
2382
2870
  // src/core/image-input.ts
2383
- import fs10 from "fs/promises";
2384
- import path8 from "path";
2871
+ import fs11 from "fs/promises";
2872
+ import path9 from "path";
2385
2873
  var IMAGE_MIME_BY_EXT = {
2386
2874
  ".jpg": "image/jpeg",
2387
2875
  ".jpeg": "image/jpeg",
@@ -2408,7 +2896,7 @@ function isHttpUrl(input) {
2408
2896
  return /^https?:\/\//i.test(input);
2409
2897
  }
2410
2898
  async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
2411
- const ext = path8.extname(filePath).toLowerCase();
2899
+ const ext = path9.extname(filePath).toLowerCase();
2412
2900
  const mime = mimeTable[ext];
2413
2901
  if (!mime) {
2414
2902
  throw new Error(
@@ -2417,7 +2905,7 @@ async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
2417
2905
  }
2418
2906
  const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
2419
2907
  const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
2420
- const buf = await fs10.readFile(filePath);
2908
+ const buf = await fs11.readFile(filePath);
2421
2909
  if (buf.byteLength > hard) {
2422
2910
  throw new Error(
2423
2911
  `${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 +3034,9 @@ var BytePlusClient = class {
2546
3034
  }
2547
3035
  apiKey;
2548
3036
  baseUrl;
2549
- async request(path57, opts = {}) {
3037
+ async request(path58, opts = {}) {
2550
3038
  const base = this.baseUrl.replace(/\/$/, "");
2551
- const url = new URL(base + path57);
3039
+ const url = new URL(base + path58);
2552
3040
  if (opts.query) {
2553
3041
  for (const [k, v] of Object.entries(opts.query)) {
2554
3042
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -2566,14 +3054,14 @@ var BytePlusClient = class {
2566
3054
  timeoutMs: opts.timeoutMs ?? 12e4
2567
3055
  });
2568
3056
  }
2569
- get(path57, query, logger) {
2570
- return this.request(path57, { method: "GET", query, logger });
3057
+ get(path58, query, logger) {
3058
+ return this.request(path58, { method: "GET", query, logger });
2571
3059
  }
2572
- post(path57, body, logger) {
2573
- return this.request(path57, { method: "POST", body, logger });
3060
+ post(path58, body, logger) {
3061
+ return this.request(path58, { method: "POST", body, logger });
2574
3062
  }
2575
- delete(path57, logger) {
2576
- return this.request(path57, { method: "DELETE", logger });
3063
+ delete(path58, logger) {
3064
+ return this.request(path58, { method: "DELETE", logger });
2577
3065
  }
2578
3066
  };
2579
3067
  function createBytePlusClient() {
@@ -2678,7 +3166,7 @@ function registerBytePlusGenerate3DCommand(parent) {
2678
3166
  process.exit(1);
2679
3167
  }
2680
3168
  const ext = inferModelFileExt(url);
2681
- const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
3169
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
2682
3170
  await downloadModelFile(url, outPath, logger);
2683
3171
  console.log(`
2684
3172
  Task: ${taskId}`);
@@ -2701,8 +3189,8 @@ Task: ${taskId}`);
2701
3189
  }
2702
3190
 
2703
3191
  // src/providers/byteplus/generators/image.ts
2704
- import fs11 from "fs";
2705
- import path10 from "path";
3192
+ import fs12 from "fs";
3193
+ import path11 from "path";
2706
3194
  var ASPECT_TO_SIZE = {
2707
3195
  "1:1": "2048x2048",
2708
3196
  "16:9": "2560x1440",
@@ -2766,14 +3254,14 @@ async function generateSeedream(client, opts) {
2766
3254
  }
2767
3255
  const ext = (() => {
2768
3256
  try {
2769
- const e = path10.extname(new URL(url).pathname);
3257
+ const e = path11.extname(new URL(url).pathname);
2770
3258
  return e || ".jpg";
2771
3259
  } catch {
2772
3260
  return ".jpg";
2773
3261
  }
2774
3262
  })();
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 });
3263
+ const filename = opts.output && items.length === 1 ? path11.resolve(opts.output) : path11.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
3264
+ fs12.mkdirSync(path11.dirname(filename), { recursive: true });
2777
3265
  await downloadFile(url, filename);
2778
3266
  saved.push({ path: filename, url, seed: item.seed });
2779
3267
  logger?.success(`Saved ${filename}`);
@@ -2861,7 +3349,7 @@ function collect2(value, prev) {
2861
3349
  }
2862
3350
 
2863
3351
  // src/providers/byteplus/commands/image-to-video.ts
2864
- import path11 from "path";
3352
+ import path12 from "path";
2865
3353
 
2866
3354
  // src/providers/byteplus/generators/video.ts
2867
3355
  function buildFlagSuffix(opts) {
@@ -2984,7 +3472,7 @@ function registerBytePlusImageToVideoCommand(parent) {
2984
3472
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
2985
3473
  process.exit(1);
2986
3474
  }
2987
- const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3475
+ const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
2988
3476
  await downloadVideo(url, outPath, logger);
2989
3477
  await maybeDownloadThumb(final, outPath, {
2990
3478
  skip: opts.thumb === false,
@@ -3010,7 +3498,7 @@ Task: ${taskId}`);
3010
3498
  }
3011
3499
 
3012
3500
  // src/providers/byteplus/commands/reference-to-video.ts
3013
- import path12 from "path";
3501
+ import path13 from "path";
3014
3502
 
3015
3503
  // src/providers/byteplus/reference-input.ts
3016
3504
  var MAX_TOTAL_REFS = 12;
@@ -3042,10 +3530,10 @@ function toRef(kind, resolved, role) {
3042
3530
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
3043
3531
  }
3044
3532
  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);
3533
+ const { path: path58, role } = parseRefSpec(spec);
3534
+ if (kind === "image") return toRef(kind, await resolveImageInput(path58, logger), role);
3535
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path58, logger), role);
3536
+ return toRef(kind, await resolveAudioInput(path58, logger), role);
3049
3537
  }
3050
3538
  async function resolveRefSpecs(specs, logger) {
3051
3539
  const out = [];
@@ -3142,7 +3630,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
3142
3630
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
3143
3631
  process.exit(1);
3144
3632
  }
3145
- const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3633
+ const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3146
3634
  await downloadVideo(url, outPath, logger);
3147
3635
  await maybeDownloadThumb(final, outPath, {
3148
3636
  skip: opts.thumb === false,
@@ -3168,7 +3656,7 @@ Task: ${taskId}`);
3168
3656
  }
3169
3657
 
3170
3658
  // src/providers/byteplus/commands/status.ts
3171
- import path13 from "path";
3659
+ import path14 from "path";
3172
3660
  function registerBytePlusStatusCommand(parent) {
3173
3661
  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
3662
  async (taskId, opts) => {
@@ -3186,7 +3674,7 @@ function registerBytePlusStatusCommand(parent) {
3186
3674
  const videoUrl2 = content?.video_url;
3187
3675
  const fileUrl = content?.file_url;
3188
3676
  if (videoUrl2) {
3189
- const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
3677
+ const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
3190
3678
  await downloadVideo(videoUrl2, outPath, logger);
3191
3679
  await maybeDownloadThumb(status, outPath, {
3192
3680
  skip: opts.thumb === false,
@@ -3196,7 +3684,7 @@ function registerBytePlusStatusCommand(parent) {
3196
3684
  console.log(`Saved: ${outPath}`);
3197
3685
  } else if (fileUrl) {
3198
3686
  const ext = inferModelFileExt(fileUrl);
3199
- const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
3687
+ const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
3200
3688
  await downloadModelFile(fileUrl, outPath, logger);
3201
3689
  console.log(`Saved: ${outPath}`);
3202
3690
  } else {
@@ -3220,7 +3708,7 @@ function registerBytePlusStatusCommand(parent) {
3220
3708
  }
3221
3709
 
3222
3710
  // src/providers/byteplus/commands/video.ts
3223
- import path14 from "path";
3711
+ import path15 from "path";
3224
3712
  function registerBytePlusVideoCommand(parent) {
3225
3713
  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
3714
  "--aspect-ratio <r>",
@@ -3262,7 +3750,7 @@ function registerBytePlusVideoCommand(parent) {
3262
3750
  logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
3263
3751
  process.exit(1);
3264
3752
  }
3265
- const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3753
+ const outPath = opts.output ?? path15.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
3266
3754
  await downloadVideo(url, outPath, logger);
3267
3755
  await maybeDownloadThumb(final, outPath, {
3268
3756
  skip: opts.thumb === false,
@@ -3548,15 +4036,15 @@ async function downloadVideo2(url) {
3548
4036
 
3549
4037
  // src/providers/cloudflare/media-output.ts
3550
4038
  init_errors();
3551
- import fs12 from "fs";
3552
- import path15 from "path";
4039
+ import fs13 from "fs";
4040
+ import path16 from "path";
3553
4041
  function destination(filename, output) {
3554
- return output ? path15.resolve(output) : path15.join(getOutputDir(), filename);
4042
+ return output ? path16.resolve(output) : path16.join(getOutputDir(), filename);
3555
4043
  }
3556
4044
  function saveBytes(bytes, filename, output) {
3557
4045
  const dest = destination(filename, output);
3558
- fs12.mkdirSync(path15.dirname(dest), { recursive: true });
3559
- fs12.writeFileSync(dest, bytes);
4046
+ fs13.mkdirSync(path16.dirname(dest), { recursive: true });
4047
+ fs13.writeFileSync(dest, bytes);
3560
4048
  return dest;
3561
4049
  }
3562
4050
  function saveBase64Image(image, output) {
@@ -3739,8 +4227,8 @@ function registerCloudflareCommands(program2) {
3739
4227
  }
3740
4228
 
3741
4229
  // src/providers/elevenlabs/client.ts
3742
- import fs13 from "fs";
3743
- import path16 from "path";
4230
+ import fs14 from "fs";
4231
+ import path17 from "path";
3744
4232
  init_errors();
3745
4233
 
3746
4234
  // src/providers/elevenlabs/models.ts
@@ -3835,11 +4323,17 @@ function apiPostJson(endpoint2, payload, apiKey, opts = {}) {
3835
4323
  }
3836
4324
  async function apiDelete(endpoint2, apiKey) {
3837
4325
  const url = `${ELEVENLABS_BASE_URL}/${endpoint2.replace(/^\//, "")}`;
3838
- const res = await fetchSameOrigin(url, {
3839
- method: "DELETE",
3840
- headers: authHeaders(apiKey),
3841
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
3842
- });
4326
+ let res;
4327
+ try {
4328
+ res = await fetchSameOrigin(url, {
4329
+ method: "DELETE",
4330
+ headers: authHeaders(apiKey),
4331
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
4332
+ });
4333
+ } catch (cause) {
4334
+ if (cause instanceof HttpError) throw cause;
4335
+ throw toFetchError(cause, url, { provider: "elevenlabs" });
4336
+ }
3843
4337
  if (!res.ok) {
3844
4338
  const text = await res.text().catch(() => "");
3845
4339
  throw new HttpError(res.status, text.slice(0, 500), url);
@@ -3872,8 +4366,7 @@ async function apiPostBinary(endpoint2, body, apiKey, opts = {}) {
3872
4366
  res = await fetchSameOrigin(url.toString(), init);
3873
4367
  } catch (cause) {
3874
4368
  if (cause instanceof HttpError) throw cause;
3875
- const msg = cause instanceof Error ? cause.message : String(cause);
3876
- throw new HttpError(0, msg, url.toString());
4369
+ throw toFetchError(cause, url.toString(), { provider: "elevenlabs" });
3877
4370
  }
3878
4371
  if (!res.ok) {
3879
4372
  const text = await res.text().catch(() => "");
@@ -3893,8 +4386,7 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
3893
4386
  });
3894
4387
  } catch (cause) {
3895
4388
  if (cause instanceof HttpError) throw cause;
3896
- const msg = cause instanceof Error ? cause.message : String(cause);
3897
- throw new HttpError(0, msg, url);
4389
+ throw toFetchError(cause, url, { provider: "elevenlabs" });
3898
4390
  }
3899
4391
  if (!res.ok) {
3900
4392
  const text = await res.text().catch(() => "");
@@ -3903,10 +4395,10 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
3903
4395
  return res.json();
3904
4396
  }
3905
4397
  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);
4398
+ const abs = path17.resolve(filePath);
4399
+ if (!fs14.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4400
+ const buf = fs14.readFileSync(abs);
4401
+ const name = fieldFilename ?? path17.basename(abs);
3910
4402
  return new File([new Uint8Array(buf)], name);
3911
4403
  }
3912
4404
  async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
@@ -3930,12 +4422,12 @@ async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
3930
4422
  function saveBytes2(opts) {
3931
4423
  const { bytes, task, ext, outputCopy, logger } = opts;
3932
4424
  const dir = getOutputDir();
3933
- const dest = path16.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
3934
- fs13.writeFileSync(dest, bytes);
4425
+ const dest = path17.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
4426
+ fs14.writeFileSync(dest, bytes);
3935
4427
  logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
3936
4428
  if (outputCopy) {
3937
- fs13.mkdirSync(path16.dirname(path16.resolve(outputCopy)), { recursive: true });
3938
- fs13.copyFileSync(dest, outputCopy);
4429
+ fs14.mkdirSync(path17.dirname(path17.resolve(outputCopy)), { recursive: true });
4430
+ fs14.copyFileSync(dest, outputCopy);
3939
4431
  }
3940
4432
  return dest;
3941
4433
  }
@@ -3974,14 +4466,14 @@ ${models.length} models`);
3974
4466
  }
3975
4467
 
3976
4468
  // src/providers/elevenlabs/commands/align.ts
3977
- import fs14 from "fs";
3978
- import path17 from "path";
4469
+ import fs15 from "fs";
4470
+ import path18 from "path";
3979
4471
  function registerAlignCommand(parent) {
3980
4472
  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
4473
  const logger = createLogger({ verbose: opts.verbose ?? false });
3982
4474
  const apiKey = requireElevenLabsKey();
3983
4475
  let text = opts.text;
3984
- if (opts.textFile) text = fs14.readFileSync(path17.resolve(opts.textFile), "utf8");
4476
+ if (opts.textFile) text = fs15.readFileSync(path18.resolve(opts.textFile), "utf8");
3985
4477
  const form = new FormData();
3986
4478
  form.append("file", readFileAsBlob(opts.input));
3987
4479
  form.append("text", text);
@@ -3989,9 +4481,9 @@ function registerAlignCommand(parent) {
3989
4481
  const data = await apiPostMultipart("forced-alignment", form, apiKey, {
3990
4482
  timeoutMs: 6e5
3991
4483
  });
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));
4484
+ const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
4485
+ fs15.mkdirSync(path18.dirname(dest), { recursive: true });
4486
+ fs15.writeFileSync(dest, JSON.stringify(data, null, 2));
3995
4487
  logger.success(`Saved alignment: ${dest}`);
3996
4488
  if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
3997
4489
  });
@@ -4033,8 +4525,8 @@ function registerCloneCommand(parent) {
4033
4525
 
4034
4526
  // src/providers/elevenlabs/commands/dub.ts
4035
4527
  init_errors();
4036
- import fs15 from "fs";
4037
- import path18 from "path";
4528
+ import fs16 from "fs";
4529
+ import path19 from "path";
4038
4530
  function registerDubCommand(parent) {
4039
4531
  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
4532
  const logger = createLogger({ verbose: opts.verbose ?? false });
@@ -4077,7 +4569,7 @@ function registerDubCommand(parent) {
4077
4569
  );
4078
4570
  logger.success(`Dub complete: ${start.dubbing_id}`);
4079
4571
  if (opts.download) {
4080
- const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
4572
+ const dest = opts.output ? path19.resolve(opts.output) : path19.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
4081
4573
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
4082
4574
  await downloadDub(url, apiKey, dest);
4083
4575
  logger.success(`Saved: ${dest}`);
@@ -4092,7 +4584,7 @@ Dubbed file: ${dest}`);
4092
4584
  const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
4093
4585
  console.log(JSON.stringify(status, null, 2));
4094
4586
  if (opts.download) {
4095
- const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
4587
+ const dest = opts.output ? path19.resolve(opts.output) : path19.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
4096
4588
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
4097
4589
  await downloadDub(url, apiKey, dest);
4098
4590
  console.log(`Saved: ${dest}`);
@@ -4100,7 +4592,7 @@ Dubbed file: ${dest}`);
4100
4592
  });
4101
4593
  }
4102
4594
  async function downloadDub(url, apiKey, dest) {
4103
- fs15.mkdirSync(path18.dirname(dest), { recursive: true });
4595
+ fs16.mkdirSync(path19.dirname(dest), { recursive: true });
4104
4596
  const { response: res } = await fetchWithValidatedRedirects(url, {
4105
4597
  headers: { "xi-api-key": apiKey },
4106
4598
  signal: AbortSignal.timeout(6e5)
@@ -4109,7 +4601,7 @@ async function downloadDub(url, apiKey, dest) {
4109
4601
  throw new Error(`Failed to download dub: HTTP ${res.status}`);
4110
4602
  }
4111
4603
  const arr = new Uint8Array(await res.arrayBuffer());
4112
- fs15.writeFileSync(dest, arr);
4604
+ fs16.writeFileSync(dest, arr);
4113
4605
  }
4114
4606
 
4115
4607
  // src/providers/elevenlabs/commands/isolate.ts
@@ -4137,8 +4629,8 @@ Isolated audio: ${dest}`);
4137
4629
 
4138
4630
  // src/providers/elevenlabs/commands/music.ts
4139
4631
  init_errors();
4140
- import fs16 from "fs";
4141
- import path19 from "path";
4632
+ import fs17 from "fs";
4633
+ import path20 from "path";
4142
4634
  function registerMusicCommand(parent) {
4143
4635
  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
4636
  "--format <fmt>",
@@ -4153,9 +4645,9 @@ function registerMusicCommand(parent) {
4153
4645
  const payload = { model_id: opts.model };
4154
4646
  if (opts.prompt) payload.prompt = opts.prompt;
4155
4647
  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"));
4648
+ const planPath = path20.resolve(opts.plan);
4649
+ if (!fs17.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
4650
+ const planJson = JSON.parse(fs17.readFileSync(planPath, "utf8"));
4159
4651
  payload.composition_plan = planJson;
4160
4652
  }
4161
4653
  if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
@@ -4214,8 +4706,8 @@ Generated SFX: ${dest}`);
4214
4706
  }
4215
4707
 
4216
4708
  // src/providers/elevenlabs/commands/transcribe.ts
4217
- import fs17 from "fs";
4218
- import path20 from "path";
4709
+ import fs18 from "fs";
4710
+ import path21 from "path";
4219
4711
  function registerTranscribeCommand(parent) {
4220
4712
  parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
4221
4713
  "--model <id>",
@@ -4238,7 +4730,7 @@ function registerTranscribeCommand(parent) {
4238
4730
  });
4239
4731
  const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
4240
4732
  const dir = getOutputDir();
4241
- const dest = opts.output ? path20.resolve(opts.output) : path20.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
4733
+ const dest = opts.output ? path21.resolve(opts.output) : path21.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
4242
4734
  let body;
4243
4735
  switch (opts.format) {
4244
4736
  case "json":
@@ -4253,8 +4745,8 @@ function registerTranscribeCommand(parent) {
4253
4745
  default:
4254
4746
  body = data.text ?? "";
4255
4747
  }
4256
- fs17.mkdirSync(path20.dirname(dest), { recursive: true });
4257
- fs17.writeFileSync(dest, body);
4748
+ fs18.mkdirSync(path21.dirname(dest), { recursive: true });
4749
+ fs18.writeFileSync(dest, body);
4258
4750
  logger.success(`Saved transcript: ${dest}`);
4259
4751
  if (data.language_code) {
4260
4752
  logger.info(
@@ -4500,8 +4992,8 @@ function registerElevenLabsCommands(program2) {
4500
4992
  }
4501
4993
 
4502
4994
  // src/providers/fal/commands/image.ts
4503
- import fs19 from "fs";
4504
- import path22 from "path";
4995
+ import fs20 from "fs";
4996
+ import path23 from "path";
4505
4997
 
4506
4998
  // src/providers/fal/models.ts
4507
4999
  var DEFAULT_FAL_BASE_URL = "https://queue.fal.run";
@@ -4520,9 +5012,9 @@ var FAL_DEFAULTS = {
4520
5012
  };
4521
5013
 
4522
5014
  // src/providers/fal/run-helpers.ts
5015
+ import fs19 from "fs";
5016
+ import path22 from "path";
4523
5017
  init_errors();
4524
- import fs18 from "fs";
4525
- import path21 from "path";
4526
5018
  init_redact();
4527
5019
 
4528
5020
  // src/providers/fal/client.ts
@@ -4575,9 +5067,9 @@ var FalClient = class {
4575
5067
  apiKey;
4576
5068
  baseUrl;
4577
5069
  /** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
4578
- async request(path57, opts = {}) {
5070
+ async request(path58, opts = {}) {
4579
5071
  const base = this.baseUrl.replace(/\/$/, "");
4580
- const suffix = path57.startsWith("/") ? path57 : `/${path57}`;
5072
+ const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
4581
5073
  return this.send(base + suffix, opts);
4582
5074
  }
4583
5075
  /**
@@ -4606,11 +5098,11 @@ var FalClient = class {
4606
5098
  timeoutMs: opts.timeoutMs ?? 12e4
4607
5099
  });
4608
5100
  }
4609
- get(path57, logger) {
4610
- return this.request(path57, { method: "GET", logger });
5101
+ get(path58, logger) {
5102
+ return this.request(path58, { method: "GET", logger });
4611
5103
  }
4612
- post(path57, body, logger) {
4613
- return this.request(path57, { method: "POST", body, logger });
5104
+ post(path58, body, logger) {
5105
+ return this.request(path58, { method: "POST", body, logger });
4614
5106
  }
4615
5107
  getAbsolute(url, logger) {
4616
5108
  return this.requestAbsolute(url, { method: "GET", logger });
@@ -4643,7 +5135,7 @@ function parseInputArg(raw) {
4643
5135
  return parseJsonInput(raw);
4644
5136
  }
4645
5137
  function isTransientError(e) {
4646
- if (e instanceof HttpError) return e.status === 0 || e.status >= 500;
5138
+ if (e instanceof HttpError) return isRetryableServerStatus(e.status);
4647
5139
  return false;
4648
5140
  }
4649
5141
  function sleep(ms, signal) {
@@ -4759,7 +5251,7 @@ function extractMediaUrls(result) {
4759
5251
  function extFromUrl(url, fallback = ".bin") {
4760
5252
  try {
4761
5253
  const u = new URL(url);
4762
- const e = path21.extname(u.pathname);
5254
+ const e = path22.extname(u.pathname);
4763
5255
  return e.length > 0 ? e : fallback;
4764
5256
  } catch {
4765
5257
  return fallback;
@@ -4772,13 +5264,13 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
4772
5264
  if (!url) continue;
4773
5265
  const ext = extFromUrl(url);
4774
5266
  const filename = `fal-${requestId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
4775
- const filepath = path21.join(outDir, filename);
5267
+ const filepath = path22.join(outDir, filename);
4776
5268
  try {
4777
5269
  await downloadFile(url, filepath);
4778
5270
  saved.push(filepath);
4779
5271
  logger.success(`Saved ${filepath}`);
4780
5272
  } catch (e) {
4781
- fs18.rmSync(filepath, { force: true });
5273
+ fs19.rmSync(filepath, { force: true });
4782
5274
  logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
4783
5275
  }
4784
5276
  }
@@ -4823,9 +5315,9 @@ function registerFalImageCommand(parent) {
4823
5315
  process.exit(1);
4824
5316
  }
4825
5317
  if (opts.output && saved[0]) {
4826
- const dest = path22.resolve(opts.output);
4827
- fs19.mkdirSync(path22.dirname(dest), { recursive: true });
4828
- fs19.copyFileSync(saved[0], dest);
5318
+ const dest = path23.resolve(opts.output);
5319
+ fs20.mkdirSync(path23.dirname(dest), { recursive: true });
5320
+ fs20.copyFileSync(saved[0], dest);
4829
5321
  logger.success(`Copied to ${dest}`);
4830
5322
  }
4831
5323
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4980,9 +5472,9 @@ function registerFalCommands(program2) {
4980
5472
 
4981
5473
  // src/core/stdin-file.ts
4982
5474
  init_errors();
4983
- import fs20 from "fs";
4984
- import os2 from "os";
4985
- import path23 from "path";
5475
+ import fs21 from "fs";
5476
+ import os3 from "os";
5477
+ import path24 from "path";
4986
5478
 
4987
5479
  // src/core/limits.ts
4988
5480
  var FILES_API_MAX_BYTES = 2e9;
@@ -5087,7 +5579,7 @@ function trackTempDir(dir) {
5087
5579
  let done = false;
5088
5580
  const remove = () => {
5089
5581
  try {
5090
- fs20.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
5582
+ fs21.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
5091
5583
  } catch {
5092
5584
  }
5093
5585
  };
@@ -5119,9 +5611,9 @@ async function stdinToTempFile(opts = {}) {
5119
5611
  }
5120
5612
  const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
5121
5613
  const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
5122
- const dir = fs20.mkdtempSync(path23.join(os2.tmpdir(), "multix-stdin-"));
5614
+ const dir = fs21.mkdtempSync(path24.join(os3.tmpdir(), "multix-stdin-"));
5123
5615
  const cleanup = trackTempDir(dir);
5124
- const rawPath = path23.join(dir, "input");
5616
+ const rawPath = path24.join(dir, "input");
5125
5617
  const hint = setTimeout(() => {
5126
5618
  process.stderr.write(`Waiting for data on stdin (\`${flag} -\`)... press Ctrl+C to cancel
5127
5619
  `);
@@ -5130,7 +5622,7 @@ async function stdinToTempFile(opts = {}) {
5130
5622
  let head = Buffer.alloc(0);
5131
5623
  let size = 0;
5132
5624
  try {
5133
- const fd = fs20.openSync(rawPath, "w", 384);
5625
+ const fd = fs21.openSync(rawPath, "w", 384);
5134
5626
  try {
5135
5627
  for await (const chunk of input) {
5136
5628
  clearTimeout(hint);
@@ -5142,17 +5634,17 @@ async function stdinToTempFile(opts = {}) {
5142
5634
  head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
5143
5635
  }
5144
5636
  let offset = 0;
5145
- while (offset < buf.length) offset += fs20.writeSync(fd, buf, offset);
5637
+ while (offset < buf.length) offset += fs21.writeSync(fd, buf, offset);
5146
5638
  size += buf.length;
5147
5639
  }
5148
5640
  } finally {
5149
5641
  clearTimeout(hint);
5150
- fs20.closeSync(fd);
5642
+ fs21.closeSync(fd);
5151
5643
  }
5152
5644
  if (size === 0) throw new ValidationError(`\`${flag} -\` was given but stdin was empty`);
5153
5645
  const detectedExt = forcedExt ?? detectExtension(head);
5154
- const finalPath = path23.join(dir, `stdin${detectedExt ?? ".bin"}`);
5155
- fs20.renameSync(rawPath, finalPath);
5646
+ const finalPath = path24.join(dir, `stdin${detectedExt ?? ".bin"}`);
5647
+ fs21.renameSync(rawPath, finalPath);
5156
5648
  return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
5157
5649
  } catch (error) {
5158
5650
  cleanup();
@@ -5170,7 +5662,7 @@ async function resolveFileInputs(files, opts = {}) {
5170
5662
  throw new ValidationError(`--stdin-ext only applies when ${flag} includes \`-\``);
5171
5663
  }
5172
5664
  for (const f of files) {
5173
- if (f !== STDIN_ARG && !fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
5665
+ if (f !== STDIN_ARG && !fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
5174
5666
  }
5175
5667
  if (stdinCount === 0) {
5176
5668
  return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
@@ -5250,6 +5742,13 @@ async function uploadFetch(url, init, timeoutMs, step) {
5250
5742
  if (redirected || timedOut && step === "upload") {
5251
5743
  throw new ProviderError(message, "gemini", { cause: err2 });
5252
5744
  }
5745
+ if (!isNetworkFetchFailure(err2)) {
5746
+ throw toFetchError(err2, FILES_BASE, {
5747
+ provider: "gemini",
5748
+ prefix: "File upload failed: ",
5749
+ target: step === "start" ? FILES_BASE : "the upload session URL"
5750
+ });
5751
+ }
5253
5752
  throw new HttpError(0, message, FILES_BASE, void 0, { cause: err2 });
5254
5753
  }
5255
5754
  if (!resp.ok) {
@@ -5458,12 +5957,12 @@ async function withGeminiKey(fn, opts = {}) {
5458
5957
  }
5459
5958
 
5460
5959
  // src/providers/gemini/local-files.ts
5461
- import fs21 from "fs";
5462
- import path25 from "path";
5960
+ import fs22 from "fs";
5961
+ import path26 from "path";
5463
5962
  init_errors();
5464
5963
 
5465
5964
  // src/providers/gemini/task-resolver.ts
5466
- import path24 from "path";
5965
+ import path25 from "path";
5467
5966
  var MIME_MAP = {
5468
5967
  // Audio
5469
5968
  ".mp3": "audio/mp3",
@@ -5504,7 +6003,7 @@ var MIME_MAP = {
5504
6003
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
5505
6004
  };
5506
6005
  function getMimeType(filePath) {
5507
- const ext = path24.extname(filePath).toLowerCase();
6006
+ const ext = path25.extname(filePath).toLowerCase();
5508
6007
  return MIME_MAP[ext] ?? "application/octet-stream";
5509
6008
  }
5510
6009
 
@@ -5524,10 +6023,10 @@ function minUploadBytesPerSec() {
5524
6023
  }
5525
6024
  async function uploadLocalFile(filePath, opts = {}) {
5526
6025
  const bytesPerSec = minUploadBytesPerSec();
5527
- const blob = await fs21.openAsBlob(filePath);
6026
+ const blob = await fs22.openAsBlob(filePath);
5528
6027
  const source = {
5529
6028
  blob,
5530
- displayName: path25.basename(filePath),
6029
+ displayName: path26.basename(filePath),
5531
6030
  mimeType: getMimeType(filePath),
5532
6031
  label: filePath
5533
6032
  };
@@ -5535,12 +6034,12 @@ async function uploadLocalFile(filePath, opts = {}) {
5535
6034
  return opts.apiKey ? upload(opts.apiKey) : withGeminiKey(upload, { logger: opts.logger });
5536
6035
  }
5537
6036
  function inlinePart(filePath) {
5538
- const data = fs21.readFileSync(filePath).toString("base64");
6037
+ const data = fs22.readFileSync(filePath).toString("base64");
5539
6038
  const mimeType = getMimeType(filePath);
5540
6039
  return { inlineData: { mimeType, data } };
5541
6040
  }
5542
6041
  function shouldUseFileApi(filePath) {
5543
- const stat = fs21.statSync(filePath);
6042
+ const stat = fs22.statSync(filePath);
5544
6043
  return stat.size > INLINE_MAX_BYTES;
5545
6044
  }
5546
6045
 
@@ -5591,13 +6090,13 @@ function getDefaultModel(task) {
5591
6090
  }
5592
6091
 
5593
6092
  // src/providers/gemini/result-formatter.ts
5594
- import fs22 from "fs";
5595
- import path26 from "path";
6093
+ import fs23 from "fs";
6094
+ import path27 from "path";
5596
6095
  function formatResults(results, format, outputPath) {
5597
6096
  const content = renderFormat(results, format);
5598
6097
  if (outputPath) {
5599
- fs22.mkdirSync(path26.dirname(path26.resolve(outputPath)), { recursive: true });
5600
- fs22.writeFileSync(outputPath, content, "utf8");
6098
+ fs23.mkdirSync(path27.dirname(path27.resolve(outputPath)), { recursive: true });
6099
+ fs23.writeFileSync(outputPath, content, "utf8");
5601
6100
  } else {
5602
6101
  process.stdout.write(content);
5603
6102
  }
@@ -5781,8 +6280,8 @@ function registerExtractCommand(parent) {
5781
6280
  init_errors();
5782
6281
 
5783
6282
  // src/providers/gemini/generators/speech.ts
5784
- import fs23 from "fs";
5785
- import path27 from "path";
6283
+ import fs24 from "fs";
6284
+ import path28 from "path";
5786
6285
 
5787
6286
  // src/providers/gemini/voices.ts
5788
6287
  var GEMINI_TTS_VOICES = [
@@ -5954,12 +6453,12 @@ function extractWavData(wav) {
5954
6453
  }
5955
6454
  function saveSpeech(outBytes, outputFormat, output, logger) {
5956
6455
  const outDir = getOutputDir();
5957
- const dest = path27.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
5958
- fs23.writeFileSync(dest, outBytes);
6456
+ const dest = path28.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
6457
+ fs24.writeFileSync(dest, outBytes);
5959
6458
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
5960
6459
  if (output) {
5961
- fs23.mkdirSync(path27.dirname(path27.resolve(output)), { recursive: true });
5962
- fs23.copyFileSync(dest, output);
6460
+ fs24.mkdirSync(path28.dirname(path28.resolve(output)), { recursive: true });
6461
+ fs24.copyFileSync(dest, output);
5963
6462
  logger?.success(`Copied to: ${output}`);
5964
6463
  }
5965
6464
  return dest;
@@ -6072,24 +6571,24 @@ function collectSpeakers(value, prev) {
6072
6571
 
6073
6572
  // src/providers/gemini/commands/generate-video.ts
6074
6573
  init_errors();
6075
- import fs25 from "fs";
6076
- import path28 from "path";
6574
+ import fs26 from "fs";
6575
+ import path29 from "path";
6077
6576
 
6078
6577
  // src/providers/gemini/veo.ts
6079
- import fs24 from "fs";
6578
+ import fs25 from "fs";
6080
6579
  init_errors();
6081
6580
  var API_HOST = "generativelanguage.googleapis.com";
6082
6581
  var BASE2 = `https://${API_HOST}/v1beta`;
6083
6582
  var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
6084
6583
  var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
6085
6584
  function veoImageFromFile(imgPath) {
6086
- if (!fs24.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
6585
+ if (!fs25.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
6087
6586
  const detected = getMimeType(imgPath);
6088
6587
  const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
6089
6588
  if (!VEO_IMAGE_TYPES.has(mimeType)) {
6090
6589
  throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
6091
6590
  }
6092
- return { bytesBase64Encoded: fs24.readFileSync(imgPath).toString("base64"), mimeType };
6591
+ return { bytesBase64Encoded: fs25.readFileSync(imgPath).toString("base64"), mimeType };
6093
6592
  }
6094
6593
  function veoFramesFromPaths(paths = []) {
6095
6594
  if (paths.length > 2) {
@@ -6215,7 +6714,7 @@ function registerGenerateVideoCommand(parent) {
6215
6714
  logger.debug(`Submitting video generation with ${model}...`);
6216
6715
  logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
6217
6716
  const frames = veoFramesFromPaths(opts.referenceImages);
6218
- const dest = path28.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
6717
+ const dest = path29.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
6219
6718
  const finalOp = await runVeo(
6220
6719
  model,
6221
6720
  {
@@ -6229,8 +6728,8 @@ function registerGenerateVideoCommand(parent) {
6229
6728
  );
6230
6729
  logger.success(`Saved video: ${dest}`);
6231
6730
  if (opts.output) {
6232
- fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
6233
- fs25.copyFileSync(dest, opts.output);
6731
+ fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
6732
+ fs26.copyFileSync(dest, opts.output);
6234
6733
  logger.success(`Copied to: ${opts.output}`);
6235
6734
  }
6236
6735
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -6246,8 +6745,8 @@ Generated video: ${dest}`);
6246
6745
 
6247
6746
  // src/providers/gemini/commands/generate.ts
6248
6747
  init_errors();
6249
- import fs26 from "fs";
6250
- import path29 from "path";
6748
+ import fs27 from "fs";
6749
+ import path30 from "path";
6251
6750
  function registerGenerateCommand(parent) {
6252
6751
  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(
6253
6752
  async (opts) => {
@@ -6295,8 +6794,8 @@ function registerGenerateCommand(parent) {
6295
6794
  const outDir = getOutputDir();
6296
6795
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
6297
6796
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
6298
- const dest = path29.join(outDir, fname);
6299
- fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
6797
+ const dest = path30.join(outDir, fname);
6798
+ fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
6300
6799
  savedFiles.push(dest);
6301
6800
  logger.success(`Saved: ${dest}`);
6302
6801
  }
@@ -6311,8 +6810,8 @@ function registerGenerateCommand(parent) {
6311
6810
  process.exit(1);
6312
6811
  }
6313
6812
  if (opts.output && savedFiles[0]) {
6314
- fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
6315
- fs26.copyFileSync(savedFiles[0], opts.output);
6813
+ fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
6814
+ fs27.copyFileSync(savedFiles[0], opts.output);
6316
6815
  logger.success(`Copied to: ${opts.output}`);
6317
6816
  }
6318
6817
  const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
@@ -6324,8 +6823,8 @@ Generated ${finalImages.length} image(s):`);
6324
6823
  }
6325
6824
 
6326
6825
  // src/providers/gemini/commands/image-to-image.ts
6327
- import fs27 from "fs";
6328
- import path30 from "path";
6826
+ import fs28 from "fs";
6827
+ import path31 from "path";
6329
6828
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
6330
6829
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
6331
6830
  function registerGeminiImageToImageCommand(parent) {
@@ -6388,14 +6887,14 @@ function registerGeminiImageToImageCommand(parent) {
6388
6887
  const img = images[i];
6389
6888
  if (!img) continue;
6390
6889
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
6391
- const dest = path30.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
6392
- fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
6890
+ const dest = path31.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
6891
+ fs28.writeFileSync(dest, Buffer.from(img.data, "base64"));
6393
6892
  saved.push(dest);
6394
6893
  logger.success(`Saved: ${dest}`);
6395
6894
  }
6396
6895
  if (opts.output && saved[0]) {
6397
- fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
6398
- fs27.copyFileSync(saved[0], opts.output);
6896
+ fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
6897
+ fs28.copyFileSync(saved[0], opts.output);
6399
6898
  logger.success(`Copied to: ${opts.output}`);
6400
6899
  }
6401
6900
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -6423,7 +6922,7 @@ function sniffImageMime(buf, url) {
6423
6922
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
6424
6923
  return "image/webp";
6425
6924
  }
6426
- const ext = path30.extname(new URL(url).pathname).toLowerCase();
6925
+ const ext = path31.extname(new URL(url).pathname).toLowerCase();
6427
6926
  if (ext === ".png") return "image/png";
6428
6927
  if (ext === ".webp") return "image/webp";
6429
6928
  if (ext === ".gif") return "image/gif";
@@ -6432,8 +6931,8 @@ function sniffImageMime(buf, url) {
6432
6931
 
6433
6932
  // src/providers/gemini/commands/image-to-video.ts
6434
6933
  init_errors();
6435
- import fs28 from "fs";
6436
- import path31 from "path";
6934
+ import fs29 from "fs";
6935
+ import path32 from "path";
6437
6936
  function registerGeminiImageToVideoCommand(parent) {
6438
6937
  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(
6439
6938
  async (imagePath, opts) => {
@@ -6453,7 +6952,7 @@ function registerGeminiImageToVideoCommand(parent) {
6453
6952
  logger.info(
6454
6953
  "Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
6455
6954
  );
6456
- const dest = path31.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
6955
+ const dest = path32.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
6457
6956
  const finalOp = await runVeo(
6458
6957
  model,
6459
6958
  {
@@ -6468,8 +6967,8 @@ function registerGeminiImageToVideoCommand(parent) {
6468
6967
  );
6469
6968
  logger.success(`Saved video: ${dest}`);
6470
6969
  if (opts.output) {
6471
- fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
6472
- fs28.copyFileSync(dest, opts.output);
6970
+ fs29.mkdirSync(path32.dirname(path32.resolve(opts.output)), { recursive: true });
6971
+ fs29.copyFileSync(dest, opts.output);
6473
6972
  logger.success(`Copied to: ${opts.output}`);
6474
6973
  }
6475
6974
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -6674,8 +7173,8 @@ var HEYGEN_OPERATIONS = {
6674
7173
  };
6675
7174
 
6676
7175
  // src/providers/heygen/node-helpers.ts
6677
- import fs29 from "fs";
6678
- import path32 from "path";
7176
+ import fs30 from "fs";
7177
+ import path33 from "path";
6679
7178
  init_errors();
6680
7179
  init_redact();
6681
7180
 
@@ -6788,12 +7287,12 @@ function describePayload(data) {
6788
7287
  );
6789
7288
  return (text ?? String(data)).slice(0, 500);
6790
7289
  }
6791
- function requireJobId(data, field, path57) {
7290
+ function requireJobId(data, field, path58) {
6792
7291
  const value = data?.[field];
6793
7292
  const ok2 = Array.isArray(value) ? value.length > 0 && value.every((v) => typeof v === "string" && v.length > 0) : typeof value === "string" && value.length > 0;
6794
7293
  if (!ok2) {
6795
7294
  throw new ProviderError(
6796
- `HeyGen ${path57} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
7295
+ `HeyGen ${path58} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
6797
7296
  "heygen"
6798
7297
  );
6799
7298
  }
@@ -6818,8 +7317,8 @@ var HeygenClient = class {
6818
7317
  this.newIdempotencyKey = opts.newIdempotencyKey ?? (() => crypto.randomUUID());
6819
7318
  }
6820
7319
  /** Build an absolute URL for a v3 path plus query parameters. */
6821
- url(path57, query) {
6822
- const suffix = path57.startsWith("/") ? path57 : `/${path57}`;
7320
+ url(path58, query) {
7321
+ const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
6823
7322
  const params = new URLSearchParams();
6824
7323
  for (const [k, v] of Object.entries(query ?? {})) {
6825
7324
  if (v !== void 0 && v !== "") params.set(k, String(v));
@@ -6841,10 +7340,10 @@ var HeygenClient = class {
6841
7340
  * `request_in_progress` are retried only when an Idempotency-Key makes the
6842
7341
  * replay safe. GET requests are left to the caller's polling loop.
6843
7342
  */
6844
- async send(path57, opts = {}) {
7343
+ async send(path58, opts = {}) {
6845
7344
  const method = opts.method ?? "GET";
6846
7345
  const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : opts.idempotencyKey ? this.newIdempotencyKey() : void 0;
6847
- const url = this.url(path57, opts.query);
7346
+ const url = this.url(path58, opts.query);
6848
7347
  for (let attempt = 1; ; attempt++) {
6849
7348
  try {
6850
7349
  return await fetchJson({
@@ -6872,24 +7371,24 @@ var HeygenClient = class {
6872
7371
  return Math.min(error.retryAfterMs, MAX_RETRY_WAIT_MS);
6873
7372
  }
6874
7373
  if (!key) return void 0;
6875
- if (error.status === 0 || error.status >= 500 || code === "request_in_progress") {
7374
+ if (isRetryableServerStatus(error.status) || code === "request_in_progress") {
6876
7375
  return Math.min(1e3 * 2 ** (attempt - 1), MAX_RETRY_WAIT_MS);
6877
7376
  }
6878
7377
  return void 0;
6879
7378
  }
6880
7379
  /** Send and unwrap `{ data }`. A 2xx body carrying `error` is treated as a failure. */
6881
- async request(path57, opts = {}) {
6882
- const json = await this.send(path57, opts);
6883
- return unwrapData(json, path57);
7380
+ async request(path58, opts = {}) {
7381
+ const json = await this.send(path58, opts);
7382
+ return unwrapData(json, path58);
6884
7383
  }
6885
- async page(path57, query) {
6886
- const json = await this.send(path57, {
7384
+ async page(path58, query) {
7385
+ const json = await this.send(path58, {
6887
7386
  query
6888
7387
  });
6889
- const items = unwrapData(json, path57);
7388
+ const items = unwrapData(json, path58);
6890
7389
  if (!Array.isArray(items)) {
6891
7390
  throw new ProviderError(
6892
- `Unexpected HeyGen response from ${path57}: data is not a list`,
7391
+ `Unexpected HeyGen response from ${path58}: data is not a list`,
6893
7392
  "heygen"
6894
7393
  );
6895
7394
  }
@@ -6907,9 +7406,9 @@ var HeygenClient = class {
6907
7406
  // ── Video Agent ───────────────────────────────────────────────────────
6908
7407
  /** POST /v3/video-agents. No Idempotency-Key: the endpoint does not document one. */
6909
7408
  async createVideoAgent(body) {
6910
- const path57 = "/v3/video-agents";
6911
- const data = await this.request(path57, { method: "POST", body });
6912
- return requireJobId(data, "session_id", path57);
7409
+ const path58 = "/v3/video-agents";
7410
+ const data = await this.request(path58, { method: "POST", body });
7411
+ return requireJobId(data, "session_id", path58);
6913
7412
  }
6914
7413
  async getVideoAgentSession(sessionId, opts = {}) {
6915
7414
  return this.request(`/v3/video-agents/${seg(sessionId, "session id")}`, opts);
@@ -6919,36 +7418,36 @@ var HeygenClient = class {
6919
7418
  }
6920
7419
  // ── Videos ────────────────────────────────────────────────────────────
6921
7420
  async createVideo(body, idempotencyKey = true) {
6922
- const path57 = "/v3/videos";
6923
- const data = await this.request(path57, {
7421
+ const path58 = "/v3/videos";
7422
+ const data = await this.request(path58, {
6924
7423
  method: "POST",
6925
7424
  body,
6926
7425
  idempotencyKey
6927
7426
  });
6928
- return requireJobId(data, "video_id", path57);
7427
+ return requireJobId(data, "video_id", path58);
6929
7428
  }
6930
7429
  async getVideo(videoId, opts = {}) {
6931
7430
  return this.request(`/v3/videos/${seg(videoId, "video id")}`, opts);
6932
7431
  }
6933
7432
  /** POST /v3/templates/{id}. Returns the created video's `VideoDetail`. */
6934
7433
  async generateFromTemplate(templateId, body, idempotencyKey = true) {
6935
- const path57 = `/v3/templates/${seg(templateId, "template id")}`;
6936
- const data = await this.request(path57, {
7434
+ const path58 = `/v3/templates/${seg(templateId, "template id")}`;
7435
+ const data = await this.request(path58, {
6937
7436
  method: "POST",
6938
7437
  body,
6939
7438
  idempotencyKey
6940
7439
  });
6941
- return requireJobId(data, "id", path57);
7440
+ return requireJobId(data, "id", path58);
6942
7441
  }
6943
7442
  // ── HeyGen Video model ────────────────────────────────────────────────
6944
7443
  async createModelVideo(body, idempotencyKey = true) {
6945
- const path57 = "/v3/models/videos";
6946
- const data = await this.request(path57, {
7444
+ const path58 = "/v3/models/videos";
7445
+ const data = await this.request(path58, {
6947
7446
  method: "POST",
6948
7447
  body,
6949
7448
  idempotencyKey
6950
7449
  });
6951
- return requireJobId(data, "video_id", path57);
7450
+ return requireJobId(data, "video_id", path58);
6952
7451
  }
6953
7452
  async getModelVideo(videoId, opts = {}) {
6954
7453
  return this.request(`/v3/models/videos/${seg(videoId, "video id")}`, opts);
@@ -6970,13 +7469,13 @@ var HeygenClient = class {
6970
7469
  }
6971
7470
  // ── Translation & lipsync ─────────────────────────────────────────────
6972
7471
  async createVideoTranslation(body, idempotencyKey = true) {
6973
- const path57 = "/v3/video-translations";
6974
- const data = await this.request(path57, {
7472
+ const path58 = "/v3/video-translations";
7473
+ const data = await this.request(path58, {
6975
7474
  method: "POST",
6976
7475
  body,
6977
7476
  idempotencyKey
6978
7477
  });
6979
- return requireJobId(data, "video_translation_ids", path57);
7478
+ return requireJobId(data, "video_translation_ids", path58);
6980
7479
  }
6981
7480
  async getVideoTranslation(id, opts = {}) {
6982
7481
  return this.request(`/v3/video-translations/${seg(id, "video translation id")}`, opts);
@@ -6989,13 +7488,13 @@ var HeygenClient = class {
6989
7488
  return data.languages.filter((l) => typeof l === "string");
6990
7489
  }
6991
7490
  async createLipsync(body, idempotencyKey = true) {
6992
- const path57 = "/v3/lipsyncs";
6993
- const data = await this.request(path57, {
7491
+ const path58 = "/v3/lipsyncs";
7492
+ const data = await this.request(path58, {
6994
7493
  method: "POST",
6995
7494
  body,
6996
7495
  idempotencyKey
6997
7496
  });
6998
- return requireJobId(data, "lipsync_id", path57);
7497
+ return requireJobId(data, "lipsync_id", path58);
6999
7498
  }
7000
7499
  async getLipsync(id, opts = {}) {
7001
7500
  return this.request(`/v3/lipsyncs/${seg(id, "lipsync id")}`, opts);
@@ -7034,8 +7533,9 @@ var HeygenClient = class {
7034
7533
  );
7035
7534
  } catch (cause) {
7036
7535
  if (cause instanceof HttpError) throw toHeygenApiError(cause);
7037
- const msg = cause instanceof Error ? cause.message : String(cause);
7038
- const err3 = toHeygenApiError(new HttpError(0, msg, url));
7536
+ const failure = toFetchError(cause, url, { provider: "heygen" });
7537
+ if (!(failure instanceof HttpError)) throw failure;
7538
+ const err3 = toHeygenApiError(failure);
7039
7539
  if (attempt >= this.maxAttempts) throw err3;
7040
7540
  await this.sleep(this.retryDelay(err3, key, attempt) ?? 1e3);
7041
7541
  continue;
@@ -7060,9 +7560,9 @@ var HeygenClient = class {
7060
7560
  }
7061
7561
  }
7062
7562
  };
7063
- function unwrapData(json, path57) {
7563
+ function unwrapData(json, path58) {
7064
7564
  if (json === null || typeof json !== "object") {
7065
- throw new ProviderError(`Unexpected HeyGen response from ${path57}: not a JSON object`, "heygen");
7565
+ throw new ProviderError(`Unexpected HeyGen response from ${path58}: not a JSON object`, "heygen");
7066
7566
  }
7067
7567
  const record = json;
7068
7568
  const error = record.error;
@@ -7074,7 +7574,7 @@ function unwrapData(json, path57) {
7074
7574
  );
7075
7575
  }
7076
7576
  if (!("data" in record) || record.data === null || record.data === void 0) {
7077
- throw new ProviderError(`Unexpected HeyGen response from ${path57}: missing data`, "heygen");
7577
+ throw new ProviderError(`Unexpected HeyGen response from ${path58}: missing data`, "heygen");
7078
7578
  }
7079
7579
  return record.data;
7080
7580
  }
@@ -7287,7 +7787,6 @@ function resumeCommand2(kind, id, opts = {}) {
7287
7787
 
7288
7788
  // src/providers/heygen/request-builders.ts
7289
7789
  init_errors();
7290
- init_redact();
7291
7790
  function oneOf(value, allowed, flag) {
7292
7791
  if (value === void 0) return void 0;
7293
7792
  if (!allowed.includes(value)) {
@@ -7316,23 +7815,6 @@ function isHttpsUrl(url) {
7316
7815
  return false;
7317
7816
  }
7318
7817
  }
7319
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
7320
- function checkBaseUrl(raw, variable) {
7321
- const value = raw.trim();
7322
- let url;
7323
- try {
7324
- url = new URL(value);
7325
- } catch {
7326
- throw new ConfigError(`${variable} must be an absolute https:// URL`);
7327
- }
7328
- const loopbackHttp = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
7329
- if (url.protocol !== "https:" && !loopbackHttp) {
7330
- throw new ConfigError(
7331
- `${variable} must use https:// (got ${redactUrl(value)}); plain http:// is only allowed for localhost, 127.0.0.1, or [::1]`
7332
- );
7333
- }
7334
- return value;
7335
- }
7336
7818
  function classifyMediaRef(value) {
7337
7819
  const v = value.trim();
7338
7820
  if (!v) throw new ValidationError("Media reference must not be empty");
@@ -7603,17 +8085,17 @@ function createHeygenClientFromEnv() {
7603
8085
  return new HeygenClient({ apiKey, baseUrl: checkBaseUrl(baseUrl, "HEYGEN_BASE_URL") });
7604
8086
  }
7605
8087
  async function uploadLocalFile2(client, filePath, logger) {
7606
- const abs = path32.resolve(filePath);
8088
+ const abs = path33.resolve(filePath);
7607
8089
  let stat;
7608
8090
  try {
7609
- stat = fs29.statSync(abs);
8091
+ stat = fs30.statSync(abs);
7610
8092
  } catch {
7611
8093
  throw new ValidationError(
7612
8094
  `File not found: ${filePath}. Use an https:// URL, asset:<asset_id>, or an existing file.`
7613
8095
  );
7614
8096
  }
7615
8097
  if (!stat.isFile()) throw new ValidationError(`Not a file: ${filePath}`);
7616
- const ext = path32.extname(abs).slice(1).toLowerCase();
8098
+ const ext = path33.extname(abs).slice(1).toLowerCase();
7617
8099
  const mimeType = ASSET_MIME_BY_EXT[ext];
7618
8100
  if (!mimeType) {
7619
8101
  throw new ValidationError(
@@ -7625,10 +8107,10 @@ async function uploadLocalFile2(client, filePath, logger) {
7625
8107
  `${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.`
7626
8108
  );
7627
8109
  }
7628
- logger.info(`Uploading ${path32.basename(abs)} to HeyGen assets...`);
8110
+ logger.info(`Uploading ${path33.basename(abs)} to HeyGen assets...`);
7629
8111
  const res = await client.uploadAsset({
7630
- data: new Uint8Array(fs29.readFileSync(abs)),
7631
- filename: path32.basename(abs),
8112
+ data: new Uint8Array(fs30.readFileSync(abs)),
8113
+ filename: path33.basename(abs),
7632
8114
  mimeType
7633
8115
  });
7634
8116
  logger.debug(`Uploaded asset ${res.asset_id} (${res.mime_type}, ${res.size_bytes} bytes)`);
@@ -7672,7 +8154,7 @@ var MEDIA_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS]);
7672
8154
  var OUTPUT_EXTS = /* @__PURE__ */ new Set([...MEDIA_EXTS, "srt", "vtt"]);
7673
8155
  function inferExtension(url, fallback) {
7674
8156
  try {
7675
- const ext = path32.extname(new URL(url).pathname).slice(1).toLowerCase();
8157
+ const ext = path33.extname(new URL(url).pathname).slice(1).toLowerCase();
7676
8158
  return OUTPUT_EXTS.has(ext) ? ext : fallback;
7677
8159
  } catch {
7678
8160
  return fallback;
@@ -7710,11 +8192,11 @@ async function downloadOutput(url, dest, resumeHint) {
7710
8192
  }
7711
8193
  function defaultOutputPath(state, ext) {
7712
8194
  const lang = state.outputLanguage ? `-${safeSlug(state.outputLanguage)}` : "";
7713
- return path32.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
8195
+ return path33.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
7714
8196
  }
7715
8197
  function statOrUndefined(p) {
7716
8198
  try {
7717
- return fs29.statSync(p);
8199
+ return fs30.statSync(p);
7718
8200
  } catch {
7719
8201
  return void 0;
7720
8202
  }
@@ -7723,29 +8205,29 @@ function isDirectoryOutput2(output, target) {
7723
8205
  if (/[\\/]$/.test(output)) return true;
7724
8206
  const stat = statOrUndefined(target);
7725
8207
  if (stat) return stat.isDirectory();
7726
- return path32.extname(target) === "";
8208
+ return path33.extname(target) === "";
7727
8209
  }
7728
8210
  function withMediaExt(file, ext) {
7729
- const given = path32.extname(file).slice(1).toLowerCase();
8211
+ const given = path33.extname(file).slice(1).toLowerCase();
7730
8212
  const mismatch = VIDEO_EXTS.has(given) && AUDIO_EXTS.has(ext) || AUDIO_EXTS.has(given) && VIDEO_EXTS.has(ext);
7731
8213
  return mismatch ? `${file.slice(0, -(given.length + 1))}.${ext}` : file;
7732
8214
  }
7733
8215
  function outputPathFor(state, ext, output, count) {
7734
8216
  if (!output) return defaultOutputPath(state, ext);
7735
- const target = path32.resolve(output);
7736
- const givenExt = path32.extname(target);
8217
+ const target = path33.resolve(output);
8218
+ const givenExt = path33.extname(target);
7737
8219
  const isTemplate = count > 1 && MEDIA_EXTS.has(givenExt.slice(1).toLowerCase());
7738
8220
  if (isDirectoryOutput2(output, target) || count > 1 && !isTemplate) {
7739
- fs29.mkdirSync(target, { recursive: true });
7740
- return path32.join(target, path32.basename(defaultOutputPath(state, ext)));
8221
+ fs30.mkdirSync(target, { recursive: true });
8222
+ return path33.join(target, path33.basename(defaultOutputPath(state, ext)));
7741
8223
  }
7742
8224
  if (!isTemplate) return withMediaExt(target, ext);
7743
- const base = path32.basename(target, givenExt);
8225
+ const base = path33.basename(target, givenExt);
7744
8226
  const suffix = safeSlug(state.outputLanguage ?? state.id);
7745
- return withMediaExt(path32.join(path32.dirname(target), `${base}-${suffix}${givenExt}`), ext);
8227
+ return withMediaExt(path33.join(path33.dirname(target), `${base}-${suffix}${givenExt}`), ext);
7746
8228
  }
7747
8229
  function claimPath(dest, state, used) {
7748
- const ext = path32.extname(dest);
8230
+ const ext = path33.extname(dest);
7749
8231
  const stem = dest.slice(0, dest.length - ext.length);
7750
8232
  let candidate = dest;
7751
8233
  for (let n = 1; used.has(candidate.toLowerCase()); n++) {
@@ -7756,7 +8238,7 @@ function claimPath(dest, state, used) {
7756
8238
  return candidate;
7757
8239
  }
7758
8240
  function captionPathFor(dest, capExt) {
7759
- const stem = path32.join(path32.dirname(dest), path32.basename(dest, path32.extname(dest)));
8241
+ const stem = path33.join(path33.dirname(dest), path33.basename(dest, path33.extname(dest)));
7760
8242
  const capDest = `${stem}.${capExt}`;
7761
8243
  return capDest.toLowerCase() === dest.toLowerCase() ? `${stem}.captions.${capExt}` : capDest;
7762
8244
  }
@@ -8134,7 +8616,7 @@ function registerHeygenModelVideoCommand(parent) {
8134
8616
 
8135
8617
  // src/providers/heygen/commands/run.ts
8136
8618
  init_errors();
8137
- import path33 from "path";
8619
+ import path34 from "path";
8138
8620
  function extractJobIds(operation, data) {
8139
8621
  const op = HEYGEN_OPERATIONS[operation];
8140
8622
  if (!op) return [];
@@ -8172,7 +8654,7 @@ function registerHeygenRunCommand(parent) {
8172
8654
  return;
8173
8655
  }
8174
8656
  const ext = inferExtension(audioUrl, "mp3");
8175
- const dest = opts.output ? path33.resolve(opts.output) : path33.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
8657
+ const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
8176
8658
  await downloadOutput(audioUrl, dest, "Re-run the command to generate a fresh URL.");
8177
8659
  console.log(`Saved: ${dest}`);
8178
8660
  return;
@@ -8302,7 +8784,7 @@ function registerHeygenTranslateCommand(parent) {
8302
8784
  }
8303
8785
 
8304
8786
  // src/providers/heygen/commands/tts.ts
8305
- import path34 from "path";
8787
+ import path35 from "path";
8306
8788
  init_redact();
8307
8789
  function registerHeygenTtsCommand(parent) {
8308
8790
  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) => {
@@ -8333,7 +8815,7 @@ function registerHeygenTtsCommand(parent) {
8333
8815
  }
8334
8816
  const ext = inferExtension(res.audio_url, "mp3");
8335
8817
  const name = `heygen-tts-${res.request_id ?? Date.now()}.${ext}`.replace(/[^\w.-]+/g, "-");
8336
- const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), name);
8818
+ const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), name);
8337
8819
  await downloadOutput(res.audio_url, dest, "Re-run the command to generate a fresh URL.");
8338
8820
  if (opts.json) console.log(JSON.stringify({ ...summary, file: dest }, null, 2));
8339
8821
  else console.log(`Saved: ${dest} (${res.duration}s, ${res.engine})`);
@@ -8452,8 +8934,8 @@ function registerHeygenCommands(program2) {
8452
8934
  }
8453
8935
 
8454
8936
  // src/providers/leonardo/commands/generate.ts
8455
- import fs30 from "fs";
8456
- import path35 from "path";
8937
+ import fs31 from "fs";
8938
+ import path36 from "path";
8457
8939
 
8458
8940
  // src/providers/leonardo/client.ts
8459
8941
  init_errors();
@@ -8531,11 +9013,11 @@ var LeonardoClient = class {
8531
9013
  }
8532
9014
  apiKey;
8533
9015
  baseUrl;
8534
- async request(path57, opts = {}) {
9016
+ async request(path58, opts = {}) {
8535
9017
  const base = this.baseUrl.replace(/\/$/, "");
8536
- const v2Override = path57.startsWith("v2:");
9018
+ const v2Override = path58.startsWith("v2:");
8537
9019
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
8538
- const finalPath = v2Override ? path57.slice(3) : path57;
9020
+ const finalPath = v2Override ? path58.slice(3) : path58;
8539
9021
  const url = new URL(finalBase + finalPath);
8540
9022
  if (opts.query) {
8541
9023
  for (const [k, v] of Object.entries(opts.query)) {
@@ -8554,14 +9036,14 @@ var LeonardoClient = class {
8554
9036
  timeoutMs: opts.timeoutMs ?? 12e4
8555
9037
  });
8556
9038
  }
8557
- get(path57, query, logger) {
8558
- return this.request(path57, { method: "GET", query, logger });
9039
+ get(path58, query, logger) {
9040
+ return this.request(path58, { method: "GET", query, logger });
8559
9041
  }
8560
- post(path57, body, logger) {
8561
- return this.request(path57, { method: "POST", body, logger });
9042
+ post(path58, body, logger) {
9043
+ return this.request(path58, { method: "POST", body, logger });
8562
9044
  }
8563
- delete(path57, logger) {
8564
- return this.request(path57, { method: "DELETE", logger });
9045
+ delete(path58, logger) {
9046
+ return this.request(path58, { method: "DELETE", logger });
8565
9047
  }
8566
9048
  };
8567
9049
  function createLeonardoClient() {
@@ -8572,7 +9054,7 @@ function createLeonardoClient() {
8572
9054
  function extFromUrl2(url, fallback = ".png") {
8573
9055
  try {
8574
9056
  const u = new URL(url);
8575
- const e = path35.extname(u.pathname);
9057
+ const e = path36.extname(u.pathname);
8576
9058
  return e.length > 0 ? e : fallback;
8577
9059
  } catch {
8578
9060
  return fallback;
@@ -8657,7 +9139,7 @@ function registerLeonardoGenerateCommand(parent) {
8657
9139
  if (!img) continue;
8658
9140
  const ext = extFromUrl2(img.url);
8659
9141
  const filename = autoFilename(generationId, i + 1, ext);
8660
- const filepath = path35.join(outDir, filename);
9142
+ const filepath = path36.join(outDir, filename);
8661
9143
  try {
8662
9144
  await downloadFile(img.url, filepath);
8663
9145
  saved.push(filepath);
@@ -8667,9 +9149,9 @@ function registerLeonardoGenerateCommand(parent) {
8667
9149
  }
8668
9150
  }
8669
9151
  if (opts.output && saved[0]) {
8670
- const dest = path35.resolve(opts.output);
8671
- fs30.mkdirSync(path35.dirname(dest), { recursive: true });
8672
- fs30.copyFileSync(saved[0], dest);
9152
+ const dest = path36.resolve(opts.output);
9153
+ fs31.mkdirSync(path36.dirname(dest), { recursive: true });
9154
+ fs31.copyFileSync(saved[0], dest);
8673
9155
  logger.success(`Copied to ${dest}`);
8674
9156
  }
8675
9157
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -8681,11 +9163,11 @@ Generated ${finalImages.length} image(s):`);
8681
9163
  }
8682
9164
 
8683
9165
  // src/providers/leonardo/commands/image-to-image.ts
8684
- import fs31 from "fs";
8685
- import path36 from "path";
9166
+ import fs32 from "fs";
9167
+ import path37 from "path";
8686
9168
  function extFromUrl3(url, fallback = ".png") {
8687
9169
  try {
8688
- return path36.extname(new URL(url).pathname) || fallback;
9170
+ return path37.extname(new URL(url).pathname) || fallback;
8689
9171
  } catch {
8690
9172
  return fallback;
8691
9173
  }
@@ -8747,7 +9229,7 @@ function registerLeonardoImageToImageCommand(parent) {
8747
9229
  if (!img) continue;
8748
9230
  const ext = extFromUrl3(img.url);
8749
9231
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
8750
- const filepath = path36.join(outDir, filename);
9232
+ const filepath = path37.join(outDir, filename);
8751
9233
  try {
8752
9234
  await downloadFile(img.url, filepath);
8753
9235
  saved.push(filepath);
@@ -8757,9 +9239,9 @@ function registerLeonardoImageToImageCommand(parent) {
8757
9239
  }
8758
9240
  }
8759
9241
  if (opts.output && saved[0]) {
8760
- const dest = path36.resolve(opts.output);
8761
- fs31.mkdirSync(path36.dirname(dest), { recursive: true });
8762
- fs31.copyFileSync(saved[0], dest);
9242
+ const dest = path37.resolve(opts.output);
9243
+ fs32.mkdirSync(path37.dirname(dest), { recursive: true });
9244
+ fs32.copyFileSync(saved[0], dest);
8763
9245
  logger.success(`Copied to ${dest}`);
8764
9246
  }
8765
9247
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -8771,7 +9253,7 @@ Generated ${finalImages.length} image(s):`);
8771
9253
  }
8772
9254
 
8773
9255
  // src/providers/leonardo/commands/video.ts
8774
- import path37 from "path";
9256
+ import path38 from "path";
8775
9257
  function v2DimsFor(resolution) {
8776
9258
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
8777
9259
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -8882,7 +9364,7 @@ async function pollAndDownload(client, jobId, opts) {
8882
9364
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
8883
9365
  process.exit(1);
8884
9366
  }
8885
- const outPath = opts.output ?? path37.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
9367
+ const outPath = opts.output ?? path38.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
8886
9368
  await downloadFile(videoUrl2, outPath);
8887
9369
  logger.success(`Saved ${outPath}`);
8888
9370
  await maybeDownloadThumb(gen, outPath, {
@@ -8988,7 +9470,7 @@ function registerLeonardoModelsCommand(parent) {
8988
9470
  }
8989
9471
 
8990
9472
  // src/providers/leonardo/commands/status.ts
8991
- import path38 from "path";
9473
+ import path39 from "path";
8992
9474
  var VIDEO_KEYS = ["motionMP4URL", "url"];
8993
9475
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
8994
9476
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -9053,7 +9535,7 @@ function registerLeonardoStatusCommand(parent) {
9053
9535
  const outDir = getOutputDir();
9054
9536
  let idx = 0;
9055
9537
  for (const url of videos) {
9056
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path38.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
9538
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
9057
9539
  await downloadFile(url, dest);
9058
9540
  logger.success(`Saved ${dest}`);
9059
9541
  await maybeDownloadThumb(gen, dest, {
@@ -9066,7 +9548,7 @@ function registerLeonardoStatusCommand(parent) {
9066
9548
  for (const url of images) {
9067
9549
  const m = url.match(IMAGE_EXT_RE2);
9068
9550
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
9069
- const dest = path38.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
9551
+ const dest = path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
9070
9552
  await downloadFile(url, dest);
9071
9553
  logger.success(`Saved ${dest}`);
9072
9554
  idx++;
@@ -9223,8 +9705,8 @@ function sleep3(ms) {
9223
9705
  }
9224
9706
 
9225
9707
  // src/providers/minimax/generators/music.ts
9226
- import fs32 from "fs";
9227
- import path39 from "path";
9708
+ import fs33 from "fs";
9709
+ import path40 from "path";
9228
9710
  async function generateMinimaxMusic(opts) {
9229
9711
  const {
9230
9712
  apiKey,
@@ -9269,14 +9751,14 @@ async function generateMinimaxMusic(opts) {
9269
9751
  }
9270
9752
  const durationMs = resp.extra_info?.music_duration ?? 0;
9271
9753
  const outDir = getOutputDir();
9272
- const dest = path39.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
9754
+ const dest = path40.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
9273
9755
  try {
9274
9756
  if (audioData.startsWith("http")) {
9275
9757
  const bytes = await fetchBytes(audioData);
9276
- fs32.writeFileSync(dest, bytes);
9758
+ fs33.writeFileSync(dest, bytes);
9277
9759
  } else {
9278
9760
  const audioBytes = Buffer.from(audioData, "hex");
9279
- fs32.writeFileSync(dest, audioBytes);
9761
+ fs33.writeFileSync(dest, audioBytes);
9280
9762
  }
9281
9763
  } catch (e) {
9282
9764
  return {
@@ -9287,8 +9769,8 @@ async function generateMinimaxMusic(opts) {
9287
9769
  const durSec = durationMs / 1e3;
9288
9770
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
9289
9771
  if (output) {
9290
- fs32.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
9291
- fs32.copyFileSync(dest, output);
9772
+ fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
9773
+ fs33.copyFileSync(dest, output);
9292
9774
  }
9293
9775
  return { status: "success", generatedAudio: dest, durationMs, model };
9294
9776
  }
@@ -9362,8 +9844,8 @@ Generated music: ${result.generatedAudio}`);
9362
9844
  init_errors();
9363
9845
 
9364
9846
  // src/providers/minimax/generators/speech.ts
9365
- import fs33 from "fs";
9366
- import path40 from "path";
9847
+ import fs34 from "fs";
9848
+ import path41 from "path";
9367
9849
  async function generateMinimaxSpeech(opts) {
9368
9850
  const {
9369
9851
  apiKey,
@@ -9410,12 +9892,12 @@ async function generateMinimaxSpeech(opts) {
9410
9892
  const audioBytes = Buffer.from(audioHex, "hex");
9411
9893
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
9412
9894
  const outDir = getOutputDir();
9413
- const dest = path40.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
9414
- fs33.writeFileSync(dest, audioBytes);
9895
+ const dest = path41.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
9896
+ fs34.writeFileSync(dest, audioBytes);
9415
9897
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
9416
9898
  if (output) {
9417
- fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
9418
- fs33.copyFileSync(dest, output);
9899
+ fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
9900
+ fs34.copyFileSync(dest, output);
9419
9901
  }
9420
9902
  return { status: "success", generatedAudio: dest, model };
9421
9903
  }
@@ -9462,8 +9944,8 @@ Generated audio: ${result.generatedAudio}`);
9462
9944
  init_errors();
9463
9945
 
9464
9946
  // src/providers/minimax/generators/video.ts
9465
- import fs34 from "fs";
9466
- import path41 from "path";
9947
+ import fs35 from "fs";
9948
+ import path42 from "path";
9467
9949
  async function generateMinimaxVideo(opts) {
9468
9950
  const {
9469
9951
  apiKey,
@@ -9509,18 +9991,18 @@ async function generateMinimaxVideo(opts) {
9509
9991
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
9510
9992
  }
9511
9993
  const outDir = getOutputDir();
9512
- const dest = path41.join(outDir, `minimax_video_${Date.now()}.mp4`);
9994
+ const dest = path42.join(outDir, `minimax_video_${Date.now()}.mp4`);
9513
9995
  try {
9514
9996
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
9515
9997
  } catch (e) {
9516
9998
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
9517
9999
  }
9518
10000
  const elapsed = (Date.now() - start) / 1e3;
9519
- const stat = fs34.statSync(dest);
10001
+ const stat = fs35.statSync(dest);
9520
10002
  const fileSizeMb = stat.size / (1024 * 1024);
9521
10003
  if (output) {
9522
- fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
9523
- fs34.copyFileSync(dest, output);
10004
+ fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
10005
+ fs35.copyFileSync(dest, output);
9524
10006
  }
9525
10007
  await maybeDownloadThumb(pollResult, dest, {
9526
10008
  skip: thumb === false,
@@ -9582,8 +10064,8 @@ Generated video: ${result.generatedVideo}`);
9582
10064
 
9583
10065
  // src/providers/minimax/generators/image.ts
9584
10066
  init_errors();
9585
- import fs35 from "fs";
9586
- import path42 from "path";
10067
+ import fs36 from "fs";
10068
+ import path43 from "path";
9587
10069
  async function generateMinimaxImage(opts) {
9588
10070
  const {
9589
10071
  apiKey,
@@ -9621,8 +10103,8 @@ async function generateMinimaxImage(opts) {
9621
10103
  try {
9622
10104
  const bytes = await fetchBytes(url);
9623
10105
  const fname = `minimax_image_${Date.now()}_${i}.png`;
9624
- const dest = path42.join(outDir, fname);
9625
- fs35.writeFileSync(dest, bytes);
10106
+ const dest = path43.join(outDir, fname);
10107
+ fs36.writeFileSync(dest, bytes);
9626
10108
  savedFiles.push(dest);
9627
10109
  logger?.success(`Saved: ${dest}`);
9628
10110
  } catch (e) {
@@ -9633,8 +10115,8 @@ async function generateMinimaxImage(opts) {
9633
10115
  }
9634
10116
  }
9635
10117
  if (output && savedFiles[0]) {
9636
- fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
9637
- fs35.copyFileSync(savedFiles[0], output);
10118
+ fs36.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
10119
+ fs36.copyFileSync(savedFiles[0], output);
9638
10120
  }
9639
10121
  return { status: "success", generatedImages: savedFiles, model };
9640
10122
  }
@@ -9673,8 +10155,8 @@ Generated ${finalImages.length} image(s):`);
9673
10155
 
9674
10156
  // src/providers/minimax/commands/image-to-image.ts
9675
10157
  init_errors();
9676
- import fs36 from "fs";
9677
- import path43 from "path";
10158
+ import fs37 from "fs";
10159
+ import path44 from "path";
9678
10160
  function registerMinimaxImageToImageCommand(parent) {
9679
10161
  parent.command("image-to-image").alias("i2i").description(
9680
10162
  "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."
@@ -9729,8 +10211,8 @@ function registerMinimaxImageToImageCommand(parent) {
9729
10211
  if (!url) continue;
9730
10212
  try {
9731
10213
  const bytes = await fetchBytes(url);
9732
- const dest = path43.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
9733
- fs36.writeFileSync(dest, bytes);
10214
+ const dest = path44.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
10215
+ fs37.writeFileSync(dest, bytes);
9734
10216
  saved.push(dest);
9735
10217
  logger.success(`Saved: ${dest}`);
9736
10218
  } catch (e) {
@@ -9741,8 +10223,8 @@ function registerMinimaxImageToImageCommand(parent) {
9741
10223
  }
9742
10224
  }
9743
10225
  if (opts.output && saved[0]) {
9744
- fs36.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
9745
- fs36.copyFileSync(saved[0], opts.output);
10226
+ fs37.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
10227
+ fs37.copyFileSync(saved[0], opts.output);
9746
10228
  logger.success(`Copied to: ${opts.output}`);
9747
10229
  }
9748
10230
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -9876,12 +10358,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
9876
10358
  }
9877
10359
 
9878
10360
  // src/providers/openai/openai-audio.ts
9879
- import fs38 from "fs";
9880
- import path45 from "path";
10361
+ import fs39 from "fs";
10362
+ import path46 from "path";
9881
10363
 
9882
10364
  // src/providers/openai/client.ts
9883
- import fs37 from "fs";
9884
- import path44 from "path";
10365
+ import fs38 from "fs";
10366
+ import path45 from "path";
9885
10367
  init_errors();
9886
10368
  var DEFAULT_TIMEOUT_MS4 = 24e4;
9887
10369
  function requireOpenAIKey() {
@@ -9920,7 +10402,7 @@ async function postOpenAIForm(pathname, form, apiKey, timeoutMs = DEFAULT_TIMEOU
9920
10402
  });
9921
10403
  } catch (cause) {
9922
10404
  if (cause instanceof HttpError) throw cause;
9923
- throw new HttpError(0, cause instanceof Error ? cause.message : String(cause), url);
10405
+ throw toFetchError(cause, url, { provider: "openai" });
9924
10406
  }
9925
10407
  if (!res.ok) {
9926
10408
  const text = await res.text().catch(() => "");
@@ -9940,7 +10422,7 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
9940
10422
  });
9941
10423
  } catch (cause) {
9942
10424
  if (cause instanceof HttpError) throw cause;
9943
- throw new HttpError(0, cause instanceof Error ? cause.message : String(cause), url);
10425
+ throw toFetchError(cause, url, { provider: "openai" });
9944
10426
  }
9945
10427
  if (!res.ok) {
9946
10428
  const text = await res.text().catch(() => "");
@@ -9949,10 +10431,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
9949
10431
  return Buffer.from(await res.arrayBuffer());
9950
10432
  }
9951
10433
  function fileFromPath(filePath) {
9952
- const abs = path44.resolve(filePath);
9953
- if (!fs37.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
9954
- const bytes = fs37.readFileSync(abs);
9955
- return new File([new Uint8Array(bytes)], path44.basename(abs));
10434
+ const abs = path45.resolve(filePath);
10435
+ if (!fs38.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
10436
+ const bytes = fs38.readFileSync(abs);
10437
+ return new File([new Uint8Array(bytes)], path45.basename(abs));
9956
10438
  }
9957
10439
 
9958
10440
  // src/providers/openai/openai-audio.ts
@@ -9991,7 +10473,7 @@ async function resolveAudioReferenceDataUrl(ref) {
9991
10473
  }
9992
10474
  function audioMimeFromReference(ref) {
9993
10475
  const pathname = new URL(ref).pathname;
9994
- const ext = path45.extname(pathname).toLowerCase();
10476
+ const ext = path46.extname(pathname).toLowerCase();
9995
10477
  switch (ext) {
9996
10478
  case ".mp3":
9997
10479
  return "audio/mpeg";
@@ -10010,12 +10492,12 @@ function audioMimeFromReference(ref) {
10010
10492
  async function generateOpenAISpeech(opts) {
10011
10493
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
10012
10494
  const ext = extFromAudioFormat(opts.outputFormat);
10013
- const dest = path45.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
10014
- fs38.writeFileSync(dest, bytes);
10495
+ const dest = path46.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
10496
+ fs39.writeFileSync(dest, bytes);
10015
10497
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
10016
10498
  if (opts.output) {
10017
- fs38.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
10018
- fs38.copyFileSync(dest, opts.output);
10499
+ fs39.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
10500
+ fs39.copyFileSync(dest, opts.output);
10019
10501
  }
10020
10502
  return dest;
10021
10503
  }
@@ -10037,9 +10519,9 @@ async function transcribeOpenAIAudio(opts) {
10037
10519
  );
10038
10520
  const text = typeof data.text === "string" ? data.text : void 0;
10039
10521
  const ext = opts.format === "text" ? "txt" : "json";
10040
- const dest = opts.output ? path45.resolve(opts.output) : path45.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
10041
- fs38.mkdirSync(path45.dirname(dest), { recursive: true });
10042
- fs38.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
10522
+ const dest = opts.output ? path46.resolve(opts.output) : path46.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
10523
+ fs39.mkdirSync(path46.dirname(dest), { recursive: true });
10524
+ fs39.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
10043
10525
  opts.logger?.success(`Saved transcript: ${dest}`);
10044
10526
  return { path: dest, text };
10045
10527
  }
@@ -10066,9 +10548,9 @@ Generated audio: ${dest}`);
10066
10548
  }
10067
10549
 
10068
10550
  // src/providers/openai/codex-image-driver.ts
10069
- import fs39 from "fs";
10070
- import os3 from "os";
10071
- import path46 from "path";
10551
+ import fs40 from "fs";
10552
+ import os4 from "os";
10553
+ import path47 from "path";
10072
10554
  import { execa } from "execa";
10073
10555
  init_errors();
10074
10556
  var defaultRunner = async (cmd, args, opts) => {
@@ -10117,12 +10599,12 @@ function sanitizeCodexEnv() {
10117
10599
  async function runCodexImageGeneration(opts) {
10118
10600
  const runner = opts.runner ?? defaultRunner;
10119
10601
  const ext = extFromImageFormat(opts.outputFormat);
10120
- const dest = opts.output ? path46.resolve(opts.output) : path46.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
10121
- fs39.mkdirSync(path46.dirname(dest), { recursive: true });
10122
- const tmp = fs39.mkdtempSync(path46.join(os3.tmpdir(), "multix-codex-image-"));
10123
- const schema = path46.join(tmp, "schema.json");
10124
- const result = path46.join(tmp, "result.json");
10125
- fs39.writeFileSync(
10602
+ const dest = opts.output ? path47.resolve(opts.output) : path47.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
10603
+ fs40.mkdirSync(path47.dirname(dest), { recursive: true });
10604
+ const tmp = fs40.mkdtempSync(path47.join(os4.tmpdir(), "multix-codex-image-"));
10605
+ const schema = path47.join(tmp, "schema.json");
10606
+ const result = path47.join(tmp, "result.json");
10607
+ fs40.writeFileSync(
10126
10608
  schema,
10127
10609
  JSON.stringify({
10128
10610
  type: "object",
@@ -10152,7 +10634,7 @@ async function runCodexImageGeneration(opts) {
10152
10634
  if (res.exitCode !== 0) {
10153
10635
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
10154
10636
  }
10155
- if (!fs39.existsSync(dest) || fs39.statSync(dest).size === 0) {
10637
+ if (!fs40.existsSync(dest) || fs40.statSync(dest).size === 0) {
10156
10638
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
10157
10639
  }
10158
10640
  return { path: dest };
@@ -10169,8 +10651,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
10169
10651
 
10170
10652
  // src/providers/openai/openai-image.ts
10171
10653
  init_errors();
10172
- import fs40 from "fs";
10173
- import path47 from "path";
10654
+ import fs41 from "fs";
10655
+ import path48 from "path";
10174
10656
  function buildImageGenerationBody(opts) {
10175
10657
  return {
10176
10658
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -10203,14 +10685,14 @@ function saveOpenAIImages(opts) {
10203
10685
  for (let i = 0; i < items.length; i++) {
10204
10686
  const b64 = items[i]?.b64_json;
10205
10687
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
10206
- const dest = path47.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
10207
- fs40.writeFileSync(dest, Buffer.from(b64, "base64"));
10688
+ const dest = path48.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
10689
+ fs41.writeFileSync(dest, Buffer.from(b64, "base64"));
10208
10690
  saved.push(dest);
10209
10691
  opts.logger?.success(`Saved: ${dest}`);
10210
10692
  }
10211
10693
  if (opts.output && saved[0]) {
10212
- fs40.mkdirSync(path47.dirname(path47.resolve(opts.output)), { recursive: true });
10213
- fs40.copyFileSync(saved[0], opts.output);
10694
+ fs41.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
10695
+ fs41.copyFileSync(saved[0], opts.output);
10214
10696
  opts.logger?.success(`Copied to: ${opts.output}`);
10215
10697
  }
10216
10698
  return saved;
@@ -10219,7 +10701,7 @@ async function resolveImageRefBytes(ref) {
10219
10701
  if (isHttpUrl(ref)) {
10220
10702
  const bytes2 = await fetchBytes(ref);
10221
10703
  const pathname = new URL(ref).pathname;
10222
- const filename = path47.basename(pathname) || "reference.png";
10704
+ const filename = path48.basename(pathname) || "reference.png";
10223
10705
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
10224
10706
  }
10225
10707
  const file = fileFromPath(ref);
@@ -10264,7 +10746,7 @@ function sniffImageMime2(buf, name) {
10264
10746
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
10265
10747
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
10266
10748
  return "image/webp";
10267
- const ext = path47.extname(name).toLowerCase();
10749
+ const ext = path48.extname(name).toLowerCase();
10268
10750
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
10269
10751
  if (ext === ".webp") return "image/webp";
10270
10752
  return "image/png";
@@ -10408,8 +10890,8 @@ function registerOpenAICommands(program2) {
10408
10890
  }
10409
10891
 
10410
10892
  // src/providers/openrouter/client.ts
10411
- import fs41 from "fs";
10412
- import path48 from "path";
10893
+ import fs42 from "fs";
10894
+ import path49 from "path";
10413
10895
  init_errors();
10414
10896
 
10415
10897
  // src/providers/openrouter/payload.ts
@@ -10573,8 +11055,8 @@ async function generateOpenRouterImage(opts) {
10573
11055
  if (!url) continue;
10574
11056
  try {
10575
11057
  const bytes = await fetchBytes(url);
10576
- const dest = path48.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
10577
- fs41.writeFileSync(dest, bytes);
11058
+ const dest = path49.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
11059
+ fs42.writeFileSync(dest, bytes);
10578
11060
  savedFiles.push(dest);
10579
11061
  logger?.success(`Saved: ${dest}`);
10580
11062
  } catch (e) {
@@ -10585,8 +11067,8 @@ async function generateOpenRouterImage(opts) {
10585
11067
  }
10586
11068
  }
10587
11069
  if (output && savedFiles[0]) {
10588
- fs41.mkdirSync(path48.dirname(path48.resolve(output)), { recursive: true });
10589
- fs41.writeFileSync(output, fs41.readFileSync(savedFiles[0]));
11070
+ fs42.mkdirSync(path49.dirname(path49.resolve(output)), { recursive: true });
11071
+ fs42.writeFileSync(output, fs42.readFileSync(savedFiles[0]));
10590
11072
  }
10591
11073
  return { status: "success", generatedImages: savedFiles, model: usedModel };
10592
11074
  }
@@ -10627,8 +11109,8 @@ Generated ${finalImages.length} image(s):`);
10627
11109
  }
10628
11110
 
10629
11111
  // src/providers/openrouter/commands/image-to-image.ts
10630
- import fs42 from "fs";
10631
- import path49 from "path";
11112
+ import fs43 from "fs";
11113
+ import path50 from "path";
10632
11114
  init_errors();
10633
11115
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
10634
11116
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
@@ -10720,14 +11202,14 @@ async function runImageToImage(opts) {
10720
11202
  const url = parsed.urls[i];
10721
11203
  if (!url) continue;
10722
11204
  const bytes = await fetchBytes(url);
10723
- const dest = path49.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
10724
- fs42.writeFileSync(dest, bytes);
11205
+ const dest = path50.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
11206
+ fs43.writeFileSync(dest, bytes);
10725
11207
  saved.push(dest);
10726
11208
  opts.logger.success(`Saved: ${dest}`);
10727
11209
  }
10728
11210
  if (opts.output && saved[0]) {
10729
- fs42.mkdirSync(path49.dirname(path49.resolve(opts.output)), { recursive: true });
10730
- fs42.copyFileSync(saved[0], opts.output);
11211
+ fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
11212
+ fs43.copyFileSync(saved[0], opts.output);
10731
11213
  opts.logger.success(`Copied to: ${opts.output}`);
10732
11214
  }
10733
11215
  return saved;
@@ -10772,13 +11254,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
10772
11254
  }
10773
11255
 
10774
11256
  // src/providers/openrouter/commands/video-status.ts
10775
- import fs43 from "fs";
10776
- import path50 from "path";
11257
+ import fs44 from "fs";
11258
+ import path51 from "path";
10777
11259
  async function downloadOpenRouterVideo(opts) {
10778
11260
  const { jobId, logger, source } = opts;
10779
11261
  const apiKey = requireOpenRouterKey();
10780
11262
  const url = videoContentUrl(jobId);
10781
- const dest = path50.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
11263
+ const dest = path51.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
10782
11264
  const { response: r } = await fetchWithValidatedRedirects(url, {
10783
11265
  headers: { Authorization: `Bearer ${apiKey}` }
10784
11266
  });
@@ -10790,14 +11272,14 @@ async function downloadOpenRouterVideo(opts) {
10790
11272
  logger.error("Empty response body");
10791
11273
  process.exit(1);
10792
11274
  }
10793
- fs43.mkdirSync(path50.dirname(dest), { recursive: true });
10794
- const ws = fs43.createWriteStream(dest);
11275
+ fs44.mkdirSync(path51.dirname(dest), { recursive: true });
11276
+ const ws = fs44.createWriteStream(dest);
10795
11277
  const { pipeline: pipeline2 } = await import("stream/promises");
10796
11278
  await pipeline2(r.body, ws);
10797
11279
  logger.success(`Saved ${dest}`);
10798
11280
  if (opts.output) {
10799
- fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
10800
- fs43.copyFileSync(dest, opts.output);
11281
+ fs44.mkdirSync(path51.dirname(path51.resolve(opts.output)), { recursive: true });
11282
+ fs44.copyFileSync(dest, opts.output);
10801
11283
  logger.success(`Copied to ${opts.output}`);
10802
11284
  }
10803
11285
  await maybeDownloadThumb(source, dest, {
@@ -10995,8 +11477,8 @@ function parseModelRef(ref, explicitVersion) {
10995
11477
  }
10996
11478
  return { kind: "version", version: trimmed };
10997
11479
  }
10998
- const [path57, inlineVersion, ...rest] = trimmed.split(":");
10999
- const [owner, name, ...extra] = (path57 ?? "").split("/");
11480
+ const [path58, inlineVersion, ...rest] = trimmed.split(":");
11481
+ const [owner, name, ...extra] = (path58 ?? "").split("/");
11000
11482
  if (rest.length > 0 || extra.length > 0 || !owner || !name) {
11001
11483
  throw new ValidationError(
11002
11484
  `Invalid Replicate model "${ref}" \u2014 expected owner/name, owner/name:version, or a 64-char version id`
@@ -11046,8 +11528,8 @@ var CREATE_RETRY_CAP_MS = 3e4;
11046
11528
  var REQUEST_TIMEOUT_MS = 6e4;
11047
11529
  var UPLOAD_TIMEOUT_MS = 3e5;
11048
11530
  var ReplicateApiError = class extends ProviderError {
11049
- constructor(message, status, retryAfterMs) {
11050
- super(message, PROVIDER);
11531
+ constructor(message, status, retryAfterMs, options) {
11532
+ super(message, PROVIDER, options);
11051
11533
  this.status = status;
11052
11534
  this.retryAfterMs = retryAfterMs;
11053
11535
  this.name = "ReplicateApiError";
@@ -11107,7 +11589,7 @@ function mapReplicateError(error, subject) {
11107
11589
  } else {
11108
11590
  message = `Replicate request for ${subject} failed (${status})${suffix}`;
11109
11591
  }
11110
- return new ReplicateApiError(message, status, retryAfterMs);
11592
+ return new ReplicateApiError(message, status, retryAfterMs, { cause: error });
11111
11593
  }
11112
11594
  var ORPHAN_HINT = "The prediction may have been created anyway (Replicate has no idempotency key); check https://replicate.com/predictions before retrying to avoid paying twice";
11113
11595
  function withOrphanHint(error) {
@@ -11115,10 +11597,14 @@ function withOrphanHint(error) {
11115
11597
  const { status } = error;
11116
11598
  const mayExist = status === 0 || status >= 200 && status < 300 || status >= 500;
11117
11599
  if (!mayExist) return error;
11118
- return new ReplicateApiError(`${error.message}. ${ORPHAN_HINT}`, status, error.retryAfterMs);
11600
+ return new ReplicateApiError(`${error.message}. ${ORPHAN_HINT}`, status, error.retryAfterMs, {
11601
+ cause: error.cause
11602
+ });
11119
11603
  }
11120
11604
  if (error instanceof ProviderError) {
11121
- return new ProviderError(`${error.message}. ${ORPHAN_HINT}`, error.provider);
11605
+ return new ProviderError(`${error.message}. ${ORPHAN_HINT}`, error.provider, {
11606
+ cause: error.cause
11607
+ });
11122
11608
  }
11123
11609
  return error;
11124
11610
  }
@@ -11156,9 +11642,9 @@ var ReplicateClient = class {
11156
11642
  return { accept: "application/json", authorization: `Bearer ${this.apiKey}`, ...extra };
11157
11643
  }
11158
11644
  /** Raw request: throws HttpError so polling can classify transient failures. */
11159
- raw(path57, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
11645
+ raw(path58, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
11160
11646
  return fetchJson({
11161
- url: `${this.baseUrl}${path57}`,
11647
+ url: `${this.baseUrl}${path58}`,
11162
11648
  method,
11163
11649
  headers: this.headers(headers2),
11164
11650
  body,
@@ -11170,7 +11656,7 @@ var ReplicateClient = class {
11170
11656
  async createPrediction(opts) {
11171
11657
  const { model, input } = opts;
11172
11658
  const isOfficial = model.kind === "official";
11173
- const path57 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
11659
+ const path58 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
11174
11660
  const body = isOfficial ? { input } : { version: model.version, input };
11175
11661
  const subject = isOfficial ? `model ${model.owner}/${model.name}` : `model version ${model.version}`;
11176
11662
  const headers2 = {};
@@ -11182,7 +11668,7 @@ var ReplicateClient = class {
11182
11668
  }
11183
11669
  for (let attempt = 0; ; attempt++) {
11184
11670
  try {
11185
- const res = await this.raw(path57, "POST", body, headers2, timeoutMs);
11671
+ const res = await this.raw(path58, "POST", body, headers2, timeoutMs);
11186
11672
  return validatePrediction(res);
11187
11673
  } catch (error) {
11188
11674
  if (error instanceof HttpError && error.status === 429 && attempt < CREATE_MAX_RETRIES) {
@@ -11272,8 +11758,7 @@ var ReplicateClient = class {
11272
11758
  );
11273
11759
  } catch (cause) {
11274
11760
  if (cause instanceof HttpError) throw mapReplicateError(cause, subject);
11275
- const msg = cause instanceof Error ? cause.message : String(cause);
11276
- throw mapReplicateError(new HttpError(0, msg, url), subject);
11761
+ throw mapReplicateError(toFetchError(cause, url, { provider: PROVIDER }), subject);
11277
11762
  }
11278
11763
  if (!response.ok) {
11279
11764
  if (response.status === 413) {
@@ -11345,145 +11830,33 @@ import { InvalidArgumentError, Option as Option2 } from "commander";
11345
11830
 
11346
11831
  // src/providers/replicate/node-outputs.ts
11347
11832
  init_errors();
11348
- import fs44 from "fs";
11349
- import path51 from "path";
11833
+ import fs45 from "fs";
11834
+ import path52 from "path";
11350
11835
  init_redact();
11351
11836
 
11352
11837
  // src/providers/replicate/output.ts
11353
11838
  init_redact();
11354
-
11355
- // src/core/url-safety.ts
11356
- init_errors();
11357
- function ipv4Octets(host) {
11358
- const parts = host.split(".");
11359
- if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
11360
- const octets = parts.map(Number);
11361
- return octets.every((o) => o <= 255) ? octets : void 0;
11362
- }
11363
- function isNonPublicIpv4([a = 0, b = 0, c2 = 0]) {
11364
- return a === 0 || // "this network" / unspecified
11365
- a === 10 || a === 127 || // loopback
11366
- a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
11367
- a === 169 && b === 254 || // link-local (cloud metadata)
11368
- a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 && c2 === 0 || // IETF protocol assignments
11369
- a === 192 && b === 0 && c2 === 2 || // TEST-NET-1
11370
- a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || // benchmarking
11371
- a === 198 && b === 51 && c2 === 100 || // TEST-NET-2
11372
- a === 203 && b === 0 && c2 === 113 || // TEST-NET-3
11373
- a >= 224;
11374
- }
11375
- function ipv6Groups(host) {
11376
- if (!/^[0-9a-f:.]+$/i.test(host) || !host.includes(":")) return void 0;
11377
- let text = host;
11378
- const tail = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
11379
- if (tail) {
11380
- const octets = ipv4Octets(tail[2]);
11381
- if (!octets) return void 0;
11382
- const [a = 0, b = 0, c2 = 0, d = 0] = octets;
11383
- text = `${tail[1]}${(a << 8 | b).toString(16)}:${(c2 << 8 | d).toString(16)}`;
11384
- }
11385
- const halves = text.split("::");
11386
- if (halves.length > 2) return void 0;
11387
- const parse = (s) => s === "" ? [] : s.split(":");
11388
- const head = parse(halves[0]);
11389
- const rest = halves.length === 2 ? parse(halves[1]) : [];
11390
- const missing = 8 - head.length - rest.length;
11391
- if (halves.length === 2 ? missing < 1 : missing !== 0) return void 0;
11392
- const all = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...rest];
11393
- if (!all.every((g) => /^[0-9a-f]{1,4}$/i.test(g))) return void 0;
11394
- return all.map((g) => Number.parseInt(g, 16));
11395
- }
11396
- function embeddedIpv4(hi, lo) {
11397
- return [hi >> 8, hi & 255, lo >> 8, lo & 255];
11398
- }
11399
- function isNonPublicIpv6(groups) {
11400
- const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
11401
- const zeroUpTo = (n) => groups.slice(0, n).every((g) => g === 0);
11402
- if (zeroUpTo(5) && g5 === 65535) return isNonPublicIpv4(embeddedIpv4(g6, g7));
11403
- if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
11404
- return isNonPublicIpv4(embeddedIpv4(g6, g7));
11405
- }
11406
- if (g0 === 8194) return isNonPublicIpv4(embeddedIpv4(g1, g2));
11407
- return g0 === 0 || // unspecified (::), loopback (::1), IPv4-compatible and other reserved
11408
- g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0 || // discard-only 100::/64
11409
- (g0 & 65024) === 64512 || // unique local fc00::/7
11410
- (g0 & 65472) === 65152 || // link-local fe80::/10
11411
- (g0 & 65472) === 65216 || // deprecated site-local fec0::/10
11412
- (g0 & 65280) === 65280;
11413
- }
11414
- function bareHost(hostname) {
11415
- const host = hostname.toLowerCase().replace(/\.$/, "");
11416
- return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
11417
- }
11418
- function isLoopbackHost(hostname) {
11419
- const host = bareHost(hostname);
11420
- if (host === "localhost" || host.endsWith(".localhost")) return true;
11421
- const octets = ipv4Octets(host);
11422
- if (octets) return octets[0] === 127;
11423
- const groups = ipv6Groups(host);
11424
- if (!groups) return false;
11425
- if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535) {
11426
- return groups[6] !== void 0 && groups[6] >> 8 === 127;
11427
- }
11428
- return groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1;
11429
- }
11430
- function isNonPublicHost(hostname) {
11431
- const host = bareHost(hostname);
11432
- if (host === "localhost" || host.endsWith(".localhost")) return true;
11433
- const octets = ipv4Octets(host);
11434
- if (octets) return isNonPublicIpv4(octets);
11435
- const groups = ipv6Groups(host);
11436
- return groups !== void 0 && isNonPublicIpv6(groups);
11437
- }
11438
- function checkUrlSafety(url, opts) {
11439
- const action = opts.action ?? "download from";
11440
- let parsed;
11441
- try {
11442
- parsed = new URL(url);
11443
- } catch {
11444
- throw new ProviderError(`Refusing to ${action} a malformed URL`, opts.provider);
11445
- }
11446
- const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
11447
- const localOk = opts.allowLocalhostHttp === true && isLoopbackHost(host);
11448
- const httpOk = parsed.protocol === "https:" || localOk && parsed.protocol === "http:";
11449
- if (!httpOk) {
11450
- throw new ProviderError(
11451
- `Refusing to ${action} ${host || "this URL"}: only https:// URLs are allowed`,
11452
- opts.provider
11453
- );
11454
- }
11455
- if (!localOk && isNonPublicHost(host)) {
11456
- throw new ProviderError(
11457
- `Refusing to ${action} ${host}: it points to a local or private address`,
11458
- opts.provider
11459
- );
11460
- }
11461
- const trusted = (opts.trustedHosts ?? []).some((t) => host === t || host.endsWith(`.${t}`));
11462
- return { host, trusted };
11463
- }
11464
-
11465
- // src/providers/replicate/output.ts
11466
11839
  var HTTP_RE = /^https?:\/\//i;
11467
11840
  var DATA_URI_RE = /^data:[^,]*,/i;
11468
11841
  function isMediaRef(value) {
11469
11842
  return typeof value === "string" && (HTTP_RE.test(value) || DATA_URI_RE.test(value));
11470
11843
  }
11471
11844
  var REPLICATE_DELIVERY_HOST = "replicate.delivery";
11472
- function checkOutputUrl(url) {
11845
+ function checkOutputUrl2(url) {
11473
11846
  return checkUrlSafety(url, { provider: "replicate", trustedHosts: [REPLICATE_DELIVERY_HOST] });
11474
11847
  }
11475
11848
  function extractOutput(output) {
11476
11849
  const result = { media: [], texts: [] };
11477
11850
  const seen = /* @__PURE__ */ new Set();
11478
- const visit = (value, path57) => {
11851
+ const visit = (value, path58) => {
11479
11852
  if (value === null || value === void 0) return;
11480
11853
  if (typeof value === "string") {
11481
- if (isMediaRef(value)) result.media.push({ url: value, path: path57 || "output" });
11482
- else if (value.length > 0) result.texts.push(path57 ? `${path57}: ${value}` : value);
11854
+ if (isMediaRef(value)) result.media.push({ url: value, path: path58 || "output" });
11855
+ else if (value.length > 0) result.texts.push(path58 ? `${path58}: ${value}` : value);
11483
11856
  return;
11484
11857
  }
11485
11858
  if (typeof value !== "object") {
11486
- result.texts.push(path57 ? `${path57}: ${String(value)}` : String(value));
11859
+ result.texts.push(path58 ? `${path58}: ${String(value)}` : String(value));
11487
11860
  return;
11488
11861
  }
11489
11862
  if (seen.has(value)) return;
@@ -11491,14 +11864,14 @@ function extractOutput(output) {
11491
11864
  if (Array.isArray(value)) {
11492
11865
  if (value.length > 0 && value.every((v) => typeof v === "string" && !isMediaRef(v))) {
11493
11866
  const joined = value.join("");
11494
- if (joined.length > 0) result.texts.push(path57 ? `${path57}: ${joined}` : joined);
11867
+ if (joined.length > 0) result.texts.push(path58 ? `${path58}: ${joined}` : joined);
11495
11868
  return;
11496
11869
  }
11497
- value.forEach((item, i) => visit(item, `${path57}[${i}]`));
11870
+ value.forEach((item, i) => visit(item, `${path58}[${i}]`));
11498
11871
  return;
11499
11872
  }
11500
11873
  for (const [key, val] of Object.entries(value)) {
11501
- visit(val, path57 ? `${path57}.${key}` : key);
11874
+ visit(val, path58 ? `${path58}.${key}` : key);
11502
11875
  }
11503
11876
  };
11504
11877
  visit(output, "");
@@ -11616,16 +11989,16 @@ var DOWNLOAD_TIMEOUT_MS = 3e5;
11616
11989
  function isDirTarget(output) {
11617
11990
  if (/[\\/]$/.test(output)) return true;
11618
11991
  try {
11619
- return fs44.statSync(output).isDirectory();
11992
+ return fs45.statSync(output).isDirectory();
11620
11993
  } catch {
11621
11994
  return false;
11622
11995
  }
11623
11996
  }
11624
11997
  function resolveDestination(predictionId, index, count, ext, opts) {
11625
- if (!opts.output) return path51.join(opts.outDir, outputFilename(predictionId, index, ext));
11626
- const output = path51.resolve(opts.output);
11627
- if (isDirTarget(opts.output)) return path51.join(output, outputFilename(predictionId, index, ext));
11628
- const givenExt = path51.extname(output);
11998
+ if (!opts.output) return path52.join(opts.outDir, outputFilename(predictionId, index, ext));
11999
+ const output = path52.resolve(opts.output);
12000
+ if (isDirTarget(opts.output)) return path52.join(output, outputFilename(predictionId, index, ext));
12001
+ const givenExt = path52.extname(output);
11629
12002
  if (count === 1) return givenExt ? output : `${output}${ext}`;
11630
12003
  const base = givenExt ? output.slice(0, -givenExt.length) : output;
11631
12004
  return `${base}-${String(index + 1).padStart(2, "0")}${ext}`;
@@ -11643,8 +12016,8 @@ async function saveOne(item, predictionId, index, count, opts) {
11643
12016
  extFromUrl4(item.url) ?? ".bin",
11644
12017
  opts
11645
12018
  );
11646
- fs44.mkdirSync(path51.dirname(dest), { recursive: true });
11647
- fs44.writeFileSync(dest, bytes);
12019
+ fs45.mkdirSync(path52.dirname(dest), { recursive: true });
12020
+ fs45.writeFileSync(dest, bytes);
11648
12021
  return dest;
11649
12022
  }
11650
12023
  const warned2 = opts.warnedHosts;
@@ -11658,7 +12031,7 @@ async function saveOne(item, predictionId, index, count, opts) {
11658
12031
  maxBytes: opts.maxBytes,
11659
12032
  fetch: opts.fetch,
11660
12033
  validateUrl: (url) => {
11661
- const { host, trusted } = checkOutputUrl(url);
12034
+ const { host, trusted } = checkOutputUrl2(url);
11662
12035
  if (!trusted && !warned2.has(host)) {
11663
12036
  warned2.add(host);
11664
12037
  opts.logger.warn(
@@ -11859,8 +12232,8 @@ function registerReplicateImageCommand(parent) {
11859
12232
 
11860
12233
  // src/providers/replicate/node-input-files.ts
11861
12234
  init_errors();
11862
- import fs45 from "fs";
11863
- import path52 from "path";
12235
+ import fs46 from "fs";
12236
+ import path53 from "path";
11864
12237
  var DATA_URI_MAX_BYTES = 256 * 1024;
11865
12238
  var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
11866
12239
  function formatMb4(bytes) {
@@ -11868,10 +12241,10 @@ function formatMb4(bytes) {
11868
12241
  }
11869
12242
  async function resolveFileInput(value, client, logger) {
11870
12243
  if (isMediaRef(value)) return value;
11871
- const filePath = path52.resolve(value);
12244
+ const filePath = path53.resolve(value);
11872
12245
  let stat;
11873
12246
  try {
11874
- stat = fs45.statSync(filePath);
12247
+ stat = fs46.statSync(filePath);
11875
12248
  } catch {
11876
12249
  throw new ValidationError(`Input file not found: ${value} (pass a local path or an https URL)`);
11877
12250
  }
@@ -11882,9 +12255,9 @@ async function resolveFileInput(value, client, logger) {
11882
12255
  `Input file ${value} is ${formatMb4(stat.size)}; Replicate accepts uploads under 100MB. Host it and pass an https URL instead.`
11883
12256
  );
11884
12257
  }
11885
- const filename = path52.basename(filePath);
12258
+ const filename = path53.basename(filePath);
11886
12259
  const contentType = mimeFromFilename(filename);
11887
- const bytes = fs45.readFileSync(filePath);
12260
+ const bytes = fs46.readFileSync(filePath);
11888
12261
  if (stat.size <= DATA_URI_MAX_BYTES) {
11889
12262
  logger.debug(`Inlining ${filename} (${stat.size} bytes) as a data URI`);
11890
12263
  return `data:${contentType};base64,${bytes.toString("base64")}`;
@@ -12011,8 +12384,8 @@ function registerReplicateCommands(program2) {
12011
12384
  }
12012
12385
 
12013
12386
  // src/commands/check.ts
12014
- import fs46 from "fs";
12015
- import os4 from "os";
12387
+ import fs47 from "fs";
12388
+ import os5 from "os";
12016
12389
 
12017
12390
  // src/commands/check-helpers/binary-check.ts
12018
12391
  import { execa as execa2 } from "execa";
@@ -12260,8 +12633,8 @@ function registerCheckCommand(program2) {
12260
12633
  logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
12261
12634
  }
12262
12635
  if (!legacyClaudeEnvEnabled()) {
12263
- const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os4.homedir();
12264
- const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs46.existsSync(f));
12636
+ const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os5.homedir();
12637
+ const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs47.existsSync(f));
12265
12638
  if (legacy.length > 0) {
12266
12639
  logger.info(
12267
12640
  `Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
@@ -12378,8 +12751,8 @@ function registerCheckCommand(program2) {
12378
12751
  }
12379
12752
 
12380
12753
  // src/commands/doc/convert.ts
12381
- import fs47 from "fs";
12382
- import path53 from "path";
12754
+ import fs48 from "fs";
12755
+ import path54 from "path";
12383
12756
 
12384
12757
  // src/commands/doc/default-prompt.ts
12385
12758
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -12397,7 +12770,7 @@ Output only the markdown content without any preamble or explanation.`;
12397
12770
  // src/commands/doc/convert.ts
12398
12771
  function autoNameBase(input) {
12399
12772
  if (input === STDIN_ARG) return "stdin";
12400
- return path53.basename(input, path53.extname(input));
12773
+ return path54.basename(input, path54.extname(input));
12401
12774
  }
12402
12775
  async function convertDocuments(opts) {
12403
12776
  const { output, autoName = false, verbose, logger } = opts;
@@ -12414,13 +12787,13 @@ async function convertDocuments(opts) {
12414
12787
  try {
12415
12788
  let outputPath;
12416
12789
  if (output) {
12417
- outputPath = path53.resolve(output);
12790
+ outputPath = path54.resolve(output);
12418
12791
  } else if (autoName && opts.inputs.length === 1 && opts.inputs[0]) {
12419
- outputPath = path53.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
12792
+ outputPath = path54.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
12420
12793
  } else {
12421
- outputPath = path53.join(getOutputDir(), "document-extraction.md");
12794
+ outputPath = path54.join(getOutputDir(), "document-extraction.md");
12422
12795
  }
12423
- fs47.mkdirSync(path53.dirname(outputPath), { recursive: true });
12796
+ fs48.mkdirSync(path54.dirname(outputPath), { recursive: true });
12424
12797
  failed = await convertResolved(inputs, { outputPath, model, prompt, verbose, logger });
12425
12798
  } finally {
12426
12799
  cleanup();
@@ -12493,7 +12866,7 @@ async function convertResolved(inputs, ctx) {
12493
12866
  `);
12494
12867
  for (const r of results) {
12495
12868
  lines.push(`
12496
- ## ${path53.basename(r.file).replace(/[<>]/g, "\\$&")}
12869
+ ## ${path54.basename(r.file).replace(/[<>]/g, "\\$&")}
12497
12870
  `);
12498
12871
  if (r.status === "success" && r.markdown) {
12499
12872
  lines.push(`${r.markdown}
@@ -12506,7 +12879,7 @@ async function convertResolved(inputs, ctx) {
12506
12879
  }
12507
12880
  lines.push("\n---\n");
12508
12881
  }
12509
- fs47.writeFileSync(outputPath, lines.join("\n"), "utf8");
12882
+ fs48.writeFileSync(outputPath, lines.join("\n"), "utf8");
12510
12883
  const succeeded = results.filter((r) => r.status === "success").length;
12511
12884
  console.log(`
12512
12885
  ${"=".repeat(50)}`);
@@ -12560,12 +12933,12 @@ function registerDocCommands(program2) {
12560
12933
 
12561
12934
  // src/commands/media/index.ts
12562
12935
  init_errors();
12563
- import fs50 from "fs";
12564
- import path56 from "path";
12936
+ import fs51 from "fs";
12937
+ import path57 from "path";
12565
12938
 
12566
12939
  // src/commands/media/batch.ts
12567
- import fs48 from "fs";
12568
- import path54 from "path";
12940
+ import fs49 from "fs";
12941
+ import path55 from "path";
12569
12942
 
12570
12943
  // src/commands/media/detect-type.ts
12571
12944
  var VIDEO_EXTS2 = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -12761,10 +13134,10 @@ async function batchOptimize(opts) {
12761
13134
  verbose,
12762
13135
  logger
12763
13136
  } = opts;
12764
- fs48.mkdirSync(outputDir, { recursive: true });
12765
- const entries = fs48.readdirSync(inputDir);
13137
+ fs49.mkdirSync(outputDir, { recursive: true });
13138
+ const entries = fs49.readdirSync(inputDir);
12766
13139
  const mediaFiles = entries.filter((f) => {
12767
- const ext = path54.extname(f).toLowerCase();
13140
+ const ext = path55.extname(f).toLowerCase();
12768
13141
  return ALL_EXTS.has(ext);
12769
13142
  });
12770
13143
  if (mediaFiles.length === 0) {
@@ -12775,9 +13148,9 @@ async function batchOptimize(opts) {
12775
13148
  let succeeded = 0;
12776
13149
  const errors = [];
12777
13150
  for (const file of mediaFiles) {
12778
- const inputPath = path54.join(inputDir, file);
12779
- const outputPath = path54.join(outputDir, file);
12780
- const ext = path54.extname(file).toLowerCase();
13151
+ const inputPath = path55.join(inputDir, file);
13152
+ const outputPath = path55.join(outputDir, file);
13153
+ const ext = path55.extname(file).toLowerCase();
12781
13154
  const kind = extToKind(ext);
12782
13155
  logger?.debug(`Processing: ${file}`);
12783
13156
  try {
@@ -12814,8 +13187,8 @@ async function batchOptimize(opts) {
12814
13187
 
12815
13188
  // src/commands/media/split-video.ts
12816
13189
  init_errors();
12817
- import fs49 from "fs";
12818
- import path55 from "path";
13190
+ import fs50 from "fs";
13191
+ import path56 from "path";
12819
13192
  async function splitVideo(opts) {
12820
13193
  await assertFfmpeg();
12821
13194
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -12826,10 +13199,10 @@ async function splitVideo(opts) {
12826
13199
  logger?.info("Video is short enough \u2014 no splitting needed");
12827
13200
  return [input];
12828
13201
  }
12829
- fs49.mkdirSync(outputDir, { recursive: true });
12830
- const ext = path55.extname(input) || ".mp4";
12831
- const baseName = path55.basename(input, ext);
12832
- const outputPattern = path55.join(outputDir, `${baseName}_chunk_%03d${ext}`);
13202
+ fs50.mkdirSync(outputDir, { recursive: true });
13203
+ const ext = path56.extname(input) || ".mp4";
13204
+ const baseName = path56.basename(input, ext);
13205
+ const outputPattern = path56.join(outputDir, `${baseName}_chunk_%03d${ext}`);
12833
13206
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
12834
13207
  const args = [
12835
13208
  "-i",
@@ -12848,8 +13221,8 @@ async function splitVideo(opts) {
12848
13221
  await runFfmpeg(args, { verbose, logger });
12849
13222
  const files = [];
12850
13223
  for (let i = 0; i < numChunks + 1; i++) {
12851
- const fname = path55.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
12852
- if (fs49.existsSync(fname)) files.push(fname);
13224
+ const fname = path56.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
13225
+ if (fs50.existsSync(fname)) files.push(fname);
12853
13226
  }
12854
13227
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
12855
13228
  return files;
@@ -12863,9 +13236,9 @@ function registerMediaCommands(program2) {
12863
13236
  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(
12864
13237
  async (opts) => {
12865
13238
  const logger = createLogger({ verbose: opts.verbose ?? false });
12866
- if (!fs50.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
12867
- fs50.mkdirSync(path56.dirname(path56.resolve(opts.output)), { recursive: true });
12868
- const ext = path56.extname(opts.input).toLowerCase();
13239
+ if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
13240
+ fs51.mkdirSync(path57.dirname(path57.resolve(opts.output)), { recursive: true });
13241
+ const ext = path57.extname(opts.input).toLowerCase();
12869
13242
  const kind = extToKind(ext);
12870
13243
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
12871
13244
  const quality = Number.parseInt(opts.quality, 10);
@@ -12905,7 +13278,7 @@ function registerMediaCommands(program2) {
12905
13278
  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(
12906
13279
  async (opts) => {
12907
13280
  const logger = createLogger({ verbose: opts.verbose ?? false });
12908
- if (!fs50.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
13281
+ if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
12909
13282
  const chunks = await splitVideo({
12910
13283
  input: opts.input,
12911
13284
  outputDir: opts.outputDir,
@@ -12921,7 +13294,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
12921
13294
  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(
12922
13295
  async (opts) => {
12923
13296
  const logger = createLogger({ verbose: opts.verbose ?? false });
12924
- if (!fs50.existsSync(opts.inputDir))
13297
+ if (!fs51.existsSync(opts.inputDir))
12925
13298
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
12926
13299
  const result = await batchOptimize({
12927
13300
  inputDir: opts.inputDir,