@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/CHANGELOG.md +14 -0
- package/README.md +6 -5
- package/dist/cli.js +956 -583
- package/package.json +1 -1
- package/skill/SKILL.md +4 -4
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1027
|
-
|
|
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
|
|
1126
|
-
if (!
|
|
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}${
|
|
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
|
|
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
|
|
1390
|
-
import
|
|
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
|
-
|
|
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
|
|
2052
|
+
const planned = path6.join(dir, `${base}_thumb.${ext}`);
|
|
1796
2053
|
try {
|
|
1797
|
-
await download(thumbUrl,
|
|
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
|
-
|
|
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, {
|
|
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) ||
|
|
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 ?
|
|
1921
|
-
return exts.map((ext, i) =>
|
|
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 =
|
|
2373
|
+
const abs = path8.resolve(output);
|
|
1924
2374
|
if (urls.length === 1) return [abs];
|
|
1925
|
-
const base = abs.slice(0, -
|
|
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 ${
|
|
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) ?
|
|
1944
|
-
|
|
1945
|
-
|
|
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:
|
|
2409
|
+
predictionId: id,
|
|
1958
2410
|
outDir: getOutputDir,
|
|
1959
2411
|
output: opts.output,
|
|
1960
2412
|
hint: cfg.hint
|
|
1961
2413
|
});
|
|
1962
|
-
const
|
|
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
|
|
1971
|
-
|
|
1972
|
-
|
|
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 ${
|
|
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 =
|
|
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, {
|
|
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 ${
|
|
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 ${
|
|
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 =
|
|
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
|
|
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 =
|
|
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, {
|
|
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
|
|
2868
|
+
import path10 from "path";
|
|
2381
2869
|
|
|
2382
2870
|
// src/core/image-input.ts
|
|
2383
|
-
import
|
|
2384
|
-
import
|
|
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 =
|
|
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
|
|
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(
|
|
3037
|
+
async request(path58, opts = {}) {
|
|
2550
3038
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
2551
|
-
const url = new URL(base +
|
|
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(
|
|
2570
|
-
return this.request(
|
|
3057
|
+
get(path58, query, logger) {
|
|
3058
|
+
return this.request(path58, { method: "GET", query, logger });
|
|
2571
3059
|
}
|
|
2572
|
-
post(
|
|
2573
|
-
return this.request(
|
|
3060
|
+
post(path58, body, logger) {
|
|
3061
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
2574
3062
|
}
|
|
2575
|
-
delete(
|
|
2576
|
-
return this.request(
|
|
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 ??
|
|
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
|
|
2705
|
-
import
|
|
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 =
|
|
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 ?
|
|
2776
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
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:
|
|
3046
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
3047
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
3048
|
-
return toRef(kind, await resolveAudioInput(
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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
|
|
3552
|
-
import
|
|
4039
|
+
import fs13 from "fs";
|
|
4040
|
+
import path16 from "path";
|
|
3553
4041
|
function destination(filename, output) {
|
|
3554
|
-
return output ?
|
|
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
|
-
|
|
3559
|
-
|
|
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
|
|
3743
|
-
import
|
|
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
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
3907
|
-
if (!
|
|
3908
|
-
const buf =
|
|
3909
|
-
const name = fieldFilename ??
|
|
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 =
|
|
3934
|
-
|
|
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
|
-
|
|
3938
|
-
|
|
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
|
|
3978
|
-
import
|
|
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 =
|
|
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 ?
|
|
3993
|
-
|
|
3994
|
-
|
|
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
|
|
4037
|
-
import
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4141
|
-
import
|
|
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 =
|
|
4157
|
-
if (!
|
|
4158
|
-
const planJson = JSON.parse(
|
|
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
|
|
4218
|
-
import
|
|
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 ?
|
|
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
|
-
|
|
4257
|
-
|
|
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
|
|
4504
|
-
import
|
|
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(
|
|
5070
|
+
async request(path58, opts = {}) {
|
|
4579
5071
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
4580
|
-
const suffix =
|
|
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(
|
|
4610
|
-
return this.request(
|
|
5101
|
+
get(path58, logger) {
|
|
5102
|
+
return this.request(path58, { method: "GET", logger });
|
|
4611
5103
|
}
|
|
4612
|
-
post(
|
|
4613
|
-
return this.request(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
4827
|
-
|
|
4828
|
-
|
|
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
|
|
4984
|
-
import
|
|
4985
|
-
import
|
|
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
|
-
|
|
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 =
|
|
5614
|
+
const dir = fs21.mkdtempSync(path24.join(os3.tmpdir(), "multix-stdin-"));
|
|
5123
5615
|
const cleanup = trackTempDir(dir);
|
|
5124
|
-
const rawPath =
|
|
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 =
|
|
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 +=
|
|
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
|
-
|
|
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 =
|
|
5155
|
-
|
|
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 && !
|
|
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
|
|
5462
|
-
import
|
|
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
|
|
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 =
|
|
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
|
|
6026
|
+
const blob = await fs22.openAsBlob(filePath);
|
|
5528
6027
|
const source = {
|
|
5529
6028
|
blob,
|
|
5530
|
-
displayName:
|
|
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 =
|
|
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 =
|
|
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
|
|
5595
|
-
import
|
|
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
|
-
|
|
5600
|
-
|
|
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
|
|
5785
|
-
import
|
|
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 =
|
|
5958
|
-
|
|
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
|
-
|
|
5962
|
-
|
|
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
|
|
6076
|
-
import
|
|
6574
|
+
import fs26 from "fs";
|
|
6575
|
+
import path29 from "path";
|
|
6077
6576
|
|
|
6078
6577
|
// src/providers/gemini/veo.ts
|
|
6079
|
-
import
|
|
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 (!
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
6233
|
-
|
|
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
|
|
6250
|
-
import
|
|
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 =
|
|
6299
|
-
|
|
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
|
-
|
|
6315
|
-
|
|
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
|
|
6328
|
-
import
|
|
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 =
|
|
6392
|
-
|
|
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
|
-
|
|
6398
|
-
|
|
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 =
|
|
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
|
|
6436
|
-
import
|
|
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 =
|
|
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
|
-
|
|
6472
|
-
|
|
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
|
|
6678
|
-
import
|
|
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,
|
|
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 ${
|
|
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(
|
|
6822
|
-
const suffix =
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
6882
|
-
const json = await this.send(
|
|
6883
|
-
return unwrapData(json,
|
|
7380
|
+
async request(path58, opts = {}) {
|
|
7381
|
+
const json = await this.send(path58, opts);
|
|
7382
|
+
return unwrapData(json, path58);
|
|
6884
7383
|
}
|
|
6885
|
-
async page(
|
|
6886
|
-
const json = await this.send(
|
|
7384
|
+
async page(path58, query) {
|
|
7385
|
+
const json = await this.send(path58, {
|
|
6887
7386
|
query
|
|
6888
7387
|
});
|
|
6889
|
-
const items = unwrapData(json,
|
|
7388
|
+
const items = unwrapData(json, path58);
|
|
6890
7389
|
if (!Array.isArray(items)) {
|
|
6891
7390
|
throw new ProviderError(
|
|
6892
|
-
`Unexpected HeyGen response from ${
|
|
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
|
|
6911
|
-
const data = await this.request(
|
|
6912
|
-
return requireJobId(data, "session_id",
|
|
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
|
|
6923
|
-
const data = await this.request(
|
|
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",
|
|
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
|
|
6936
|
-
const data = await this.request(
|
|
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",
|
|
7440
|
+
return requireJobId(data, "id", path58);
|
|
6942
7441
|
}
|
|
6943
7442
|
// ── HeyGen Video model ────────────────────────────────────────────────
|
|
6944
7443
|
async createModelVideo(body, idempotencyKey = true) {
|
|
6945
|
-
const
|
|
6946
|
-
const data = await this.request(
|
|
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",
|
|
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
|
|
6974
|
-
const data = await this.request(
|
|
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",
|
|
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
|
|
6993
|
-
const data = await this.request(
|
|
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",
|
|
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
|
|
7038
|
-
|
|
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,
|
|
7563
|
+
function unwrapData(json, path58) {
|
|
7064
7564
|
if (json === null || typeof json !== "object") {
|
|
7065
|
-
throw new ProviderError(`Unexpected HeyGen response from ${
|
|
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 ${
|
|
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 =
|
|
8088
|
+
const abs = path33.resolve(filePath);
|
|
7607
8089
|
let stat;
|
|
7608
8090
|
try {
|
|
7609
|
-
stat =
|
|
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 =
|
|
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 ${
|
|
8110
|
+
logger.info(`Uploading ${path33.basename(abs)} to HeyGen assets...`);
|
|
7629
8111
|
const res = await client.uploadAsset({
|
|
7630
|
-
data: new Uint8Array(
|
|
7631
|
-
filename:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
8208
|
+
return path33.extname(target) === "";
|
|
7727
8209
|
}
|
|
7728
8210
|
function withMediaExt(file, ext) {
|
|
7729
|
-
const given =
|
|
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 =
|
|
7736
|
-
const givenExt =
|
|
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
|
-
|
|
7740
|
-
return
|
|
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 =
|
|
8225
|
+
const base = path33.basename(target, givenExt);
|
|
7744
8226
|
const suffix = safeSlug(state.outputLanguage ?? state.id);
|
|
7745
|
-
return withMediaExt(
|
|
8227
|
+
return withMediaExt(path33.join(path33.dirname(target), `${base}-${suffix}${givenExt}`), ext);
|
|
7746
8228
|
}
|
|
7747
8229
|
function claimPath(dest, state, used) {
|
|
7748
|
-
const ext =
|
|
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 =
|
|
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
|
|
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 ?
|
|
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
|
|
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 ?
|
|
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
|
|
8456
|
-
import
|
|
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(
|
|
9016
|
+
async request(path58, opts = {}) {
|
|
8535
9017
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
8536
|
-
const v2Override =
|
|
9018
|
+
const v2Override = path58.startsWith("v2:");
|
|
8537
9019
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
8538
|
-
const finalPath = v2Override ?
|
|
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(
|
|
8558
|
-
return this.request(
|
|
9039
|
+
get(path58, query, logger) {
|
|
9040
|
+
return this.request(path58, { method: "GET", query, logger });
|
|
8559
9041
|
}
|
|
8560
|
-
post(
|
|
8561
|
-
return this.request(
|
|
9042
|
+
post(path58, body, logger) {
|
|
9043
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
8562
9044
|
}
|
|
8563
|
-
delete(
|
|
8564
|
-
return this.request(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8671
|
-
|
|
8672
|
-
|
|
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
|
|
8685
|
-
import
|
|
9166
|
+
import fs32 from "fs";
|
|
9167
|
+
import path37 from "path";
|
|
8686
9168
|
function extFromUrl3(url, fallback = ".png") {
|
|
8687
9169
|
try {
|
|
8688
|
-
return
|
|
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 =
|
|
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 =
|
|
8761
|
-
|
|
8762
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
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 :
|
|
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 =
|
|
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
|
|
9227
|
-
import
|
|
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 =
|
|
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
|
-
|
|
9758
|
+
fs33.writeFileSync(dest, bytes);
|
|
9277
9759
|
} else {
|
|
9278
9760
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
9279
|
-
|
|
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
|
-
|
|
9291
|
-
|
|
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
|
|
9366
|
-
import
|
|
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 =
|
|
9414
|
-
|
|
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
|
-
|
|
9418
|
-
|
|
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
|
|
9466
|
-
import
|
|
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 =
|
|
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 =
|
|
10001
|
+
const stat = fs35.statSync(dest);
|
|
9520
10002
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
9521
10003
|
if (output) {
|
|
9522
|
-
|
|
9523
|
-
|
|
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
|
|
9586
|
-
import
|
|
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 =
|
|
9625
|
-
|
|
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
|
-
|
|
9637
|
-
|
|
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
|
|
9677
|
-
import
|
|
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 =
|
|
9733
|
-
|
|
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
|
-
|
|
9745
|
-
|
|
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
|
|
9880
|
-
import
|
|
10361
|
+
import fs39 from "fs";
|
|
10362
|
+
import path46 from "path";
|
|
9881
10363
|
|
|
9882
10364
|
// src/providers/openai/client.ts
|
|
9883
|
-
import
|
|
9884
|
-
import
|
|
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
|
|
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
|
|
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 =
|
|
9953
|
-
if (!
|
|
9954
|
-
const bytes =
|
|
9955
|
-
return new File([new Uint8Array(bytes)],
|
|
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 =
|
|
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 =
|
|
10014
|
-
|
|
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
|
-
|
|
10018
|
-
|
|
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 ?
|
|
10041
|
-
|
|
10042
|
-
|
|
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
|
|
10070
|
-
import
|
|
10071
|
-
import
|
|
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 ?
|
|
10121
|
-
|
|
10122
|
-
const tmp =
|
|
10123
|
-
const schema =
|
|
10124
|
-
const result =
|
|
10125
|
-
|
|
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 (!
|
|
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
|
|
10173
|
-
import
|
|
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 =
|
|
10207
|
-
|
|
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
|
-
|
|
10213
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
10412
|
-
import
|
|
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 =
|
|
10577
|
-
|
|
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
|
-
|
|
10589
|
-
|
|
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
|
|
10631
|
-
import
|
|
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 =
|
|
10724
|
-
|
|
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
|
-
|
|
10730
|
-
|
|
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
|
|
10776
|
-
import
|
|
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 =
|
|
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
|
-
|
|
10794
|
-
const ws =
|
|
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
|
-
|
|
10800
|
-
|
|
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 [
|
|
10999
|
-
const [owner, name, ...extra] = (
|
|
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(
|
|
11645
|
+
raw(path58, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
11160
11646
|
return fetchJson({
|
|
11161
|
-
url: `${this.baseUrl}${
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
11349
|
-
import
|
|
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
|
|
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,
|
|
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:
|
|
11482
|
-
else if (value.length > 0) result.texts.push(
|
|
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(
|
|
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(
|
|
11867
|
+
if (joined.length > 0) result.texts.push(path58 ? `${path58}: ${joined}` : joined);
|
|
11495
11868
|
return;
|
|
11496
11869
|
}
|
|
11497
|
-
value.forEach((item, i) => visit(item, `${
|
|
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,
|
|
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
|
|
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
|
|
11626
|
-
const output =
|
|
11627
|
-
if (isDirTarget(opts.output)) return
|
|
11628
|
-
const givenExt =
|
|
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
|
-
|
|
11647
|
-
|
|
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 } =
|
|
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
|
|
11863
|
-
import
|
|
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 =
|
|
12244
|
+
const filePath = path53.resolve(value);
|
|
11872
12245
|
let stat;
|
|
11873
12246
|
try {
|
|
11874
|
-
stat =
|
|
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 =
|
|
12258
|
+
const filename = path53.basename(filePath);
|
|
11886
12259
|
const contentType = mimeFromFilename(filename);
|
|
11887
|
-
const bytes =
|
|
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
|
|
12015
|
-
import
|
|
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 :
|
|
12264
|
-
const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((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
|
|
12382
|
-
import
|
|
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
|
|
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 =
|
|
12790
|
+
outputPath = path54.resolve(output);
|
|
12418
12791
|
} else if (autoName && opts.inputs.length === 1 && opts.inputs[0]) {
|
|
12419
|
-
outputPath =
|
|
12792
|
+
outputPath = path54.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
|
|
12420
12793
|
} else {
|
|
12421
|
-
outputPath =
|
|
12794
|
+
outputPath = path54.join(getOutputDir(), "document-extraction.md");
|
|
12422
12795
|
}
|
|
12423
|
-
|
|
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
|
-
## ${
|
|
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
|
-
|
|
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
|
|
12564
|
-
import
|
|
12936
|
+
import fs51 from "fs";
|
|
12937
|
+
import path57 from "path";
|
|
12565
12938
|
|
|
12566
12939
|
// src/commands/media/batch.ts
|
|
12567
|
-
import
|
|
12568
|
-
import
|
|
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
|
-
|
|
12765
|
-
const entries =
|
|
13137
|
+
fs49.mkdirSync(outputDir, { recursive: true });
|
|
13138
|
+
const entries = fs49.readdirSync(inputDir);
|
|
12766
13139
|
const mediaFiles = entries.filter((f) => {
|
|
12767
|
-
const ext =
|
|
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 =
|
|
12779
|
-
const outputPath =
|
|
12780
|
-
const ext =
|
|
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
|
|
12818
|
-
import
|
|
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
|
-
|
|
12830
|
-
const ext =
|
|
12831
|
-
const baseName =
|
|
12832
|
-
const outputPattern =
|
|
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 =
|
|
12852
|
-
if (
|
|
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 (!
|
|
12867
|
-
|
|
12868
|
-
const ext =
|
|
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 (!
|
|
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 (!
|
|
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,
|