@mrgoonie/multix 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/cli.js +519 -399
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,28 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/core/redact.ts
|
|
13
|
+
function redact(key) {
|
|
14
|
+
if (key.length <= 10) return "***";
|
|
15
|
+
return `${key.slice(0, 6)}...${key.slice(-4)}`;
|
|
16
|
+
}
|
|
17
|
+
function redactUrl(url) {
|
|
18
|
+
try {
|
|
19
|
+
const u = new URL(url);
|
|
20
|
+
if (u.origin === "null") return `${u.protocol}[redacted]`;
|
|
21
|
+
const suffix = u.search || u.hash ? "?[redacted]" : "";
|
|
22
|
+
return `${u.origin}${u.pathname}${suffix}`;
|
|
23
|
+
} catch {
|
|
24
|
+
const cut = url.search(/[?#]/);
|
|
25
|
+
return cut === -1 ? url : `${url.slice(0, cut)}?[redacted]`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var init_redact = __esm({
|
|
29
|
+
"src/core/redact.ts"() {
|
|
30
|
+
"use strict";
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
12
34
|
// src/core/errors.ts
|
|
13
35
|
var errors_exports = {};
|
|
14
36
|
__export(errors_exports, {
|
|
@@ -22,6 +44,7 @@ var MultixError, ConfigError, ProviderError, ValidationError, HttpError;
|
|
|
22
44
|
var init_errors = __esm({
|
|
23
45
|
"src/core/errors.ts"() {
|
|
24
46
|
"use strict";
|
|
47
|
+
init_redact();
|
|
25
48
|
MultixError = class extends Error {
|
|
26
49
|
constructor(message, code) {
|
|
27
50
|
super(message);
|
|
@@ -54,117 +77,24 @@ var init_errors = __esm({
|
|
|
54
77
|
}
|
|
55
78
|
};
|
|
56
79
|
HttpError = class extends MultixError {
|
|
57
|
-
constructor(status, snippet, url) {
|
|
58
|
-
super(`HTTP ${status} from ${url}: ${snippet}`, "HTTP_ERROR");
|
|
80
|
+
constructor(status, snippet, url, retryAfterMs) {
|
|
81
|
+
super(`HTTP ${status} from ${redactUrl(url)}: ${snippet}`, "HTTP_ERROR");
|
|
59
82
|
this.status = status;
|
|
60
83
|
this.snippet = snippet;
|
|
61
|
-
this.
|
|
84
|
+
this.retryAfterMs = retryAfterMs;
|
|
85
|
+
this.url = redactUrl(url);
|
|
86
|
+
Object.defineProperty(this, "rawUrl", { value: url, enumerable: false });
|
|
62
87
|
this.name = "HttpError";
|
|
63
88
|
}
|
|
64
89
|
status;
|
|
65
90
|
snippet;
|
|
91
|
+
retryAfterMs;
|
|
92
|
+
/** Request URL with query string and fragment redacted — safe to log or serialize. */
|
|
66
93
|
url;
|
|
67
94
|
};
|
|
68
95
|
}
|
|
69
96
|
});
|
|
70
97
|
|
|
71
|
-
// src/core/http-client.ts
|
|
72
|
-
var http_client_exports = {};
|
|
73
|
-
__export(http_client_exports, {
|
|
74
|
-
downloadFile: () => downloadFile,
|
|
75
|
-
fetchBytes: () => fetchBytes,
|
|
76
|
-
httpJson: () => httpJson
|
|
77
|
-
});
|
|
78
|
-
import { once } from "events";
|
|
79
|
-
import fs4 from "fs";
|
|
80
|
-
import path4 from "path";
|
|
81
|
-
import { pipeline } from "stream/promises";
|
|
82
|
-
async function httpJson(opts) {
|
|
83
|
-
const { url, method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
84
|
-
const init = {
|
|
85
|
-
method,
|
|
86
|
-
headers: {
|
|
87
|
-
"Content-Type": "application/json",
|
|
88
|
-
...headers
|
|
89
|
-
},
|
|
90
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
91
|
-
};
|
|
92
|
-
if (body !== void 0) {
|
|
93
|
-
init.body = JSON.stringify(body);
|
|
94
|
-
}
|
|
95
|
-
let response;
|
|
96
|
-
try {
|
|
97
|
-
response = await globalThis.fetch(url, init);
|
|
98
|
-
} catch (cause) {
|
|
99
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
100
|
-
throw new HttpError(0, msg, url);
|
|
101
|
-
}
|
|
102
|
-
if (!response.ok) {
|
|
103
|
-
let snippet = "";
|
|
104
|
-
try {
|
|
105
|
-
const text = await response.text();
|
|
106
|
-
snippet = text.slice(0, SNIPPET_MAX);
|
|
107
|
-
} catch {
|
|
108
|
-
snippet = "(could not read body)";
|
|
109
|
-
}
|
|
110
|
-
throw new HttpError(response.status, snippet, url);
|
|
111
|
-
}
|
|
112
|
-
return response.json();
|
|
113
|
-
}
|
|
114
|
-
async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
115
|
-
fs4.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
116
|
-
let response;
|
|
117
|
-
try {
|
|
118
|
-
response = await globalThis.fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
119
|
-
} catch (cause) {
|
|
120
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
121
|
-
throw new HttpError(0, msg, url);
|
|
122
|
-
}
|
|
123
|
-
if (!response.ok) {
|
|
124
|
-
throw new HttpError(response.status, "(download failed)", url);
|
|
125
|
-
}
|
|
126
|
-
if (!response.body) {
|
|
127
|
-
throw new HttpError(0, "Response body is null", url);
|
|
128
|
-
}
|
|
129
|
-
const fileStream = fs4.createWriteStream(dest);
|
|
130
|
-
try {
|
|
131
|
-
await pipeline(response.body, fileStream);
|
|
132
|
-
} catch (err2) {
|
|
133
|
-
if (!fileStream.closed) await once(fileStream, "close");
|
|
134
|
-
throw err2;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
async function fetchBytes(urlOrDataUri) {
|
|
138
|
-
if (urlOrDataUri.startsWith("data:")) {
|
|
139
|
-
const commaIdx = urlOrDataUri.indexOf(",");
|
|
140
|
-
if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
|
|
141
|
-
const encoded = urlOrDataUri.slice(commaIdx + 1);
|
|
142
|
-
return Buffer.from(encoded, "base64");
|
|
143
|
-
}
|
|
144
|
-
let response;
|
|
145
|
-
try {
|
|
146
|
-
response = await globalThis.fetch(urlOrDataUri, {
|
|
147
|
-
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
148
|
-
});
|
|
149
|
-
} catch (cause) {
|
|
150
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
151
|
-
throw new HttpError(0, msg, urlOrDataUri);
|
|
152
|
-
}
|
|
153
|
-
if (!response.ok) {
|
|
154
|
-
throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
|
|
155
|
-
}
|
|
156
|
-
return Buffer.from(await response.arrayBuffer());
|
|
157
|
-
}
|
|
158
|
-
var DEFAULT_TIMEOUT_MS, SNIPPET_MAX;
|
|
159
|
-
var init_http_client = __esm({
|
|
160
|
-
"src/core/http-client.ts"() {
|
|
161
|
-
"use strict";
|
|
162
|
-
init_errors();
|
|
163
|
-
DEFAULT_TIMEOUT_MS = 12e4;
|
|
164
|
-
SNIPPET_MAX = 500;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
98
|
// src/cli.ts
|
|
169
99
|
import { createRequire as createRequire2 } from "module";
|
|
170
100
|
import { Command } from "commander";
|
|
@@ -346,6 +276,7 @@ async function poll(opts) {
|
|
|
346
276
|
}
|
|
347
277
|
|
|
348
278
|
// src/core/env-loader.ts
|
|
279
|
+
init_redact();
|
|
349
280
|
import fs3 from "fs";
|
|
350
281
|
import os from "os";
|
|
351
282
|
import path3 from "path";
|
|
@@ -368,14 +299,138 @@ function loadEnv() {
|
|
|
368
299
|
function resolveKey(name) {
|
|
369
300
|
return process.env[name] || void 0;
|
|
370
301
|
}
|
|
371
|
-
function redact(key) {
|
|
372
|
-
if (key.length <= 10) return "***";
|
|
373
|
-
return `${key.slice(0, 6)}...${key.slice(-4)}`;
|
|
374
|
-
}
|
|
375
302
|
|
|
376
303
|
// src/providers/byteplus/client.ts
|
|
377
304
|
init_errors();
|
|
378
|
-
|
|
305
|
+
|
|
306
|
+
// src/core/http-client.ts
|
|
307
|
+
init_errors();
|
|
308
|
+
import { once } from "events";
|
|
309
|
+
import fs4 from "fs";
|
|
310
|
+
import path4 from "path";
|
|
311
|
+
import { pipeline } from "stream/promises";
|
|
312
|
+
|
|
313
|
+
// src/core/fetch-json.ts
|
|
314
|
+
init_errors();
|
|
315
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
316
|
+
var SNIPPET_MAX = 500;
|
|
317
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
318
|
+
if (!value) return void 0;
|
|
319
|
+
const trimmed = value.trim();
|
|
320
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1e3);
|
|
321
|
+
const date = Date.parse(trimmed);
|
|
322
|
+
if (Number.isNaN(date)) return void 0;
|
|
323
|
+
return Math.max(0, date - now);
|
|
324
|
+
}
|
|
325
|
+
async function httpErrorFromResponse(response, url) {
|
|
326
|
+
let snippet = "";
|
|
327
|
+
try {
|
|
328
|
+
const text = await response.text();
|
|
329
|
+
snippet = text.slice(0, SNIPPET_MAX);
|
|
330
|
+
} catch {
|
|
331
|
+
snippet = "(could not read body)";
|
|
332
|
+
}
|
|
333
|
+
return new HttpError(
|
|
334
|
+
response.status,
|
|
335
|
+
snippet,
|
|
336
|
+
url,
|
|
337
|
+
parseRetryAfter(response.headers.get("retry-after"))
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
async function fetchJson(opts) {
|
|
341
|
+
const { url, method = "GET", headers: headers2 = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
342
|
+
const doFetch = opts.fetch ?? ((input, init2) => globalThis.fetch(input, init2));
|
|
343
|
+
const init = {
|
|
344
|
+
method,
|
|
345
|
+
headers: {
|
|
346
|
+
"Content-Type": "application/json",
|
|
347
|
+
...headers2
|
|
348
|
+
},
|
|
349
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
350
|
+
};
|
|
351
|
+
if (body !== void 0) {
|
|
352
|
+
init.body = JSON.stringify(body);
|
|
353
|
+
}
|
|
354
|
+
let response;
|
|
355
|
+
try {
|
|
356
|
+
response = await doFetch(url, init);
|
|
357
|
+
} catch (cause) {
|
|
358
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
359
|
+
throw new HttpError(0, msg, url);
|
|
360
|
+
}
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
throw await httpErrorFromResponse(response, url);
|
|
363
|
+
}
|
|
364
|
+
let text;
|
|
365
|
+
try {
|
|
366
|
+
text = await response.text();
|
|
367
|
+
} catch (cause) {
|
|
368
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
369
|
+
throw new HttpError(0, `Failed to read response body: ${msg}`, url);
|
|
370
|
+
}
|
|
371
|
+
if (text.trim() === "") return void 0;
|
|
372
|
+
try {
|
|
373
|
+
return JSON.parse(text);
|
|
374
|
+
} catch {
|
|
375
|
+
throw new HttpError(
|
|
376
|
+
response.status,
|
|
377
|
+
`Invalid JSON response: ${text.slice(0, SNIPPET_MAX)}`,
|
|
378
|
+
url
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/core/http-client.ts
|
|
384
|
+
async function httpJson(opts) {
|
|
385
|
+
return fetchJson(opts);
|
|
386
|
+
}
|
|
387
|
+
async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS, headers2) {
|
|
388
|
+
fs4.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
389
|
+
let response;
|
|
390
|
+
try {
|
|
391
|
+
response = await globalThis.fetch(url, {
|
|
392
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
393
|
+
...headers2 ? { headers: headers2 } : {}
|
|
394
|
+
});
|
|
395
|
+
} catch (cause) {
|
|
396
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
397
|
+
throw new HttpError(0, msg, url);
|
|
398
|
+
}
|
|
399
|
+
if (!response.ok) {
|
|
400
|
+
throw new HttpError(response.status, "(download failed)", url);
|
|
401
|
+
}
|
|
402
|
+
if (!response.body) {
|
|
403
|
+
throw new HttpError(0, "Response body is null", url);
|
|
404
|
+
}
|
|
405
|
+
const fileStream = fs4.createWriteStream(dest);
|
|
406
|
+
try {
|
|
407
|
+
await pipeline(response.body, fileStream);
|
|
408
|
+
} catch (err2) {
|
|
409
|
+
if (!fileStream.closed) await once(fileStream, "close");
|
|
410
|
+
throw err2;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function fetchBytes(urlOrDataUri) {
|
|
414
|
+
if (urlOrDataUri.startsWith("data:")) {
|
|
415
|
+
const commaIdx = urlOrDataUri.indexOf(",");
|
|
416
|
+
if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
|
|
417
|
+
const encoded = urlOrDataUri.slice(commaIdx + 1);
|
|
418
|
+
return Buffer.from(encoded, "base64");
|
|
419
|
+
}
|
|
420
|
+
let response;
|
|
421
|
+
try {
|
|
422
|
+
response = await globalThis.fetch(urlOrDataUri, {
|
|
423
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
424
|
+
});
|
|
425
|
+
} catch (cause) {
|
|
426
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
427
|
+
throw new HttpError(0, msg, urlOrDataUri);
|
|
428
|
+
}
|
|
429
|
+
if (!response.ok) {
|
|
430
|
+
throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
|
|
431
|
+
}
|
|
432
|
+
return Buffer.from(await response.arrayBuffer());
|
|
433
|
+
}
|
|
379
434
|
|
|
380
435
|
// src/providers/byteplus/models.ts
|
|
381
436
|
var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
|
|
@@ -455,7 +510,6 @@ function createBytePlusClient() {
|
|
|
455
510
|
}
|
|
456
511
|
|
|
457
512
|
// src/providers/byteplus/generators/three-d.ts
|
|
458
|
-
init_http_client();
|
|
459
513
|
function buildThreeDTaskBody(opts) {
|
|
460
514
|
const promptText = (opts.prompt ?? "").trim();
|
|
461
515
|
const flagPart = opts.flags?.trim() ?? "";
|
|
@@ -763,7 +817,6 @@ function finalizeGeneratedImages(files, opts) {
|
|
|
763
817
|
}
|
|
764
818
|
|
|
765
819
|
// src/providers/byteplus/generators/image.ts
|
|
766
|
-
init_http_client();
|
|
767
820
|
import fs7 from "fs";
|
|
768
821
|
import path7 from "path";
|
|
769
822
|
var ASPECT_TO_SIZE = {
|
|
@@ -927,7 +980,6 @@ function collect2(value, prev) {
|
|
|
927
980
|
import path9 from "path";
|
|
928
981
|
|
|
929
982
|
// src/core/video-thumb.ts
|
|
930
|
-
init_http_client();
|
|
931
983
|
import fs8 from "fs";
|
|
932
984
|
import path8 from "path";
|
|
933
985
|
var THUMB_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -1012,7 +1064,6 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
|
|
|
1012
1064
|
}
|
|
1013
1065
|
|
|
1014
1066
|
// src/providers/byteplus/generators/video.ts
|
|
1015
|
-
init_http_client();
|
|
1016
1067
|
function buildFlagSuffix(opts) {
|
|
1017
1068
|
const parts = [];
|
|
1018
1069
|
if (opts.resolution) parts.push(`--rs ${opts.resolution}`);
|
|
@@ -1823,7 +1874,6 @@ function registerCloudflareCommands(program2) {
|
|
|
1823
1874
|
import fs10 from "fs";
|
|
1824
1875
|
import path14 from "path";
|
|
1825
1876
|
init_errors();
|
|
1826
|
-
init_http_client();
|
|
1827
1877
|
|
|
1828
1878
|
// src/providers/elevenlabs/models.ts
|
|
1829
1879
|
var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
|
|
@@ -1932,19 +1982,19 @@ async function apiPostBinary(endpoint2, body, apiKey, opts = {}) {
|
|
|
1932
1982
|
if (opts.query) {
|
|
1933
1983
|
for (const [k, v] of Object.entries(opts.query)) url.searchParams.set(k, v);
|
|
1934
1984
|
}
|
|
1935
|
-
const
|
|
1985
|
+
const headers2 = authHeaders(apiKey);
|
|
1936
1986
|
let init;
|
|
1937
1987
|
if (body instanceof FormData) {
|
|
1938
1988
|
init = {
|
|
1939
1989
|
method: "POST",
|
|
1940
|
-
headers,
|
|
1990
|
+
headers: headers2,
|
|
1941
1991
|
body,
|
|
1942
1992
|
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
1943
1993
|
};
|
|
1944
1994
|
} else {
|
|
1945
1995
|
init = {
|
|
1946
1996
|
method: "POST",
|
|
1947
|
-
headers: { ...
|
|
1997
|
+
headers: { ...headers2, "Content-Type": "application/json" },
|
|
1948
1998
|
body: JSON.stringify(body),
|
|
1949
1999
|
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
1950
2000
|
};
|
|
@@ -2113,7 +2163,6 @@ function registerCloneCommand(parent) {
|
|
|
2113
2163
|
|
|
2114
2164
|
// src/providers/elevenlabs/commands/dub.ts
|
|
2115
2165
|
init_errors();
|
|
2116
|
-
init_http_client();
|
|
2117
2166
|
import fs12 from "fs";
|
|
2118
2167
|
import path16 from "path";
|
|
2119
2168
|
function registerDubCommand(parent) {
|
|
@@ -2582,7 +2631,7 @@ function registerElevenLabsCommands(program2) {
|
|
|
2582
2631
|
}
|
|
2583
2632
|
|
|
2584
2633
|
// src/providers/fal/commands/image.ts
|
|
2585
|
-
import
|
|
2634
|
+
import fs17 from "fs";
|
|
2586
2635
|
import path20 from "path";
|
|
2587
2636
|
|
|
2588
2637
|
// src/providers/fal/models.ts
|
|
@@ -2597,15 +2646,43 @@ var FAL_DEFAULTS = {
|
|
|
2597
2646
|
};
|
|
2598
2647
|
|
|
2599
2648
|
// src/providers/fal/run-helpers.ts
|
|
2649
|
+
import fs16 from "fs";
|
|
2650
|
+
import path19 from "path";
|
|
2651
|
+
|
|
2652
|
+
// src/core/json-input.ts
|
|
2600
2653
|
init_errors();
|
|
2601
|
-
init_http_client();
|
|
2602
2654
|
import fs15 from "fs";
|
|
2603
|
-
|
|
2655
|
+
function parseJsonInput(raw, flag = "--input") {
|
|
2656
|
+
let text;
|
|
2657
|
+
if (raw.startsWith("@")) {
|
|
2658
|
+
try {
|
|
2659
|
+
text = fs15.readFileSync(raw.slice(1), "utf8");
|
|
2660
|
+
} catch (e) {
|
|
2661
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2662
|
+
throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
|
|
2663
|
+
}
|
|
2664
|
+
} else {
|
|
2665
|
+
text = raw;
|
|
2666
|
+
}
|
|
2667
|
+
try {
|
|
2668
|
+
const parsed = JSON.parse(text);
|
|
2669
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2670
|
+
throw new Error("input must be a JSON object");
|
|
2671
|
+
}
|
|
2672
|
+
return parsed;
|
|
2673
|
+
} catch (e) {
|
|
2674
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2675
|
+
throw new ValidationError(`Invalid ${flag} JSON: ${msg}`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
// src/providers/fal/run-helpers.ts
|
|
2680
|
+
init_redact();
|
|
2604
2681
|
|
|
2605
2682
|
// src/providers/fal/client.ts
|
|
2606
2683
|
init_errors();
|
|
2607
2684
|
init_errors();
|
|
2608
|
-
|
|
2685
|
+
init_redact();
|
|
2609
2686
|
function requireFalKey() {
|
|
2610
2687
|
const key = resolveKey("FAL_KEY");
|
|
2611
2688
|
if (!key) {
|
|
@@ -2631,7 +2708,7 @@ var FalClient = class {
|
|
|
2631
2708
|
}
|
|
2632
2709
|
/** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
|
|
2633
2710
|
async requestAbsolute(url, opts = {}) {
|
|
2634
|
-
opts.logger?.debug(`${opts.method ?? "GET"} ${url}`);
|
|
2711
|
+
opts.logger?.debug(`${opts.method ?? "GET"} ${redactUrl(url)}`);
|
|
2635
2712
|
return httpJson({
|
|
2636
2713
|
url,
|
|
2637
2714
|
method: opts.method ?? "GET",
|
|
@@ -2677,17 +2754,7 @@ function resultPath(model, requestId) {
|
|
|
2677
2754
|
|
|
2678
2755
|
// src/providers/fal/run-helpers.ts
|
|
2679
2756
|
function parseInputArg(raw) {
|
|
2680
|
-
|
|
2681
|
-
try {
|
|
2682
|
-
const parsed = JSON.parse(text);
|
|
2683
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2684
|
-
throw new Error("input must be a JSON object");
|
|
2685
|
-
}
|
|
2686
|
-
return parsed;
|
|
2687
|
-
} catch (e) {
|
|
2688
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2689
|
-
throw new ValidationError(`Invalid --input JSON: ${msg}`);
|
|
2690
|
-
}
|
|
2757
|
+
return parseJsonInput(raw);
|
|
2691
2758
|
}
|
|
2692
2759
|
function isTransientError(e) {
|
|
2693
2760
|
if (e instanceof HttpError) return e.status === 0 || e.status >= 500;
|
|
@@ -2818,8 +2885,8 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
|
|
|
2818
2885
|
saved.push(filepath);
|
|
2819
2886
|
logger.success(`Saved ${filepath}`);
|
|
2820
2887
|
} catch (e) {
|
|
2821
|
-
|
|
2822
|
-
logger.warn(`Download failed for ${url}: ${e instanceof Error ? e.message : e}`);
|
|
2888
|
+
fs16.rmSync(filepath, { force: true });
|
|
2889
|
+
logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
|
|
2823
2890
|
}
|
|
2824
2891
|
}
|
|
2825
2892
|
return saved;
|
|
@@ -2864,8 +2931,8 @@ function registerFalImageCommand(parent) {
|
|
|
2864
2931
|
}
|
|
2865
2932
|
if (opts.output && saved[0]) {
|
|
2866
2933
|
const dest = path20.resolve(opts.output);
|
|
2867
|
-
|
|
2868
|
-
|
|
2934
|
+
fs17.mkdirSync(path20.dirname(dest), { recursive: true });
|
|
2935
|
+
fs17.copyFileSync(saved[0], dest);
|
|
2869
2936
|
logger.success(`Copied to ${dest}`);
|
|
2870
2937
|
}
|
|
2871
2938
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -3020,13 +3087,12 @@ function registerFalCommands(program2) {
|
|
|
3020
3087
|
|
|
3021
3088
|
// src/providers/gemini/commands/analyze.ts
|
|
3022
3089
|
init_errors();
|
|
3023
|
-
import
|
|
3090
|
+
import fs20 from "fs";
|
|
3024
3091
|
|
|
3025
3092
|
// src/providers/gemini/client.ts
|
|
3026
|
-
import
|
|
3093
|
+
import fs18 from "fs";
|
|
3027
3094
|
import path22 from "path";
|
|
3028
3095
|
init_errors();
|
|
3029
|
-
init_http_client();
|
|
3030
3096
|
|
|
3031
3097
|
// src/providers/gemini/task-resolver.ts
|
|
3032
3098
|
import path21 from "path";
|
|
@@ -3096,7 +3162,7 @@ async function uploadFile(filePath, opts = {}) {
|
|
|
3096
3162
|
const apiKey = requireApiKey();
|
|
3097
3163
|
const { timeoutMs = 3e5, logger } = opts;
|
|
3098
3164
|
const mimeType = getMimeType(filePath);
|
|
3099
|
-
const fileBytes =
|
|
3165
|
+
const fileBytes = fs18.readFileSync(filePath);
|
|
3100
3166
|
const displayName = path22.basename(filePath);
|
|
3101
3167
|
const boundary = `multix_${Date.now()}`;
|
|
3102
3168
|
const metaJson = JSON.stringify({ file: { displayName, mimeType } });
|
|
@@ -3213,12 +3279,12 @@ function extractImages(resp) {
|
|
|
3213
3279
|
).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
|
|
3214
3280
|
}
|
|
3215
3281
|
function inlinePart(filePath) {
|
|
3216
|
-
const data =
|
|
3282
|
+
const data = fs18.readFileSync(filePath).toString("base64");
|
|
3217
3283
|
const mimeType = getMimeType(filePath);
|
|
3218
3284
|
return { inlineData: { mimeType, data } };
|
|
3219
3285
|
}
|
|
3220
3286
|
function shouldUseFileApi(filePath) {
|
|
3221
|
-
const stat =
|
|
3287
|
+
const stat = fs18.statSync(filePath);
|
|
3222
3288
|
return stat.size > 20 * 1024 * 1024;
|
|
3223
3289
|
}
|
|
3224
3290
|
function sleep2(ms) {
|
|
@@ -3272,13 +3338,13 @@ function getDefaultModel(task) {
|
|
|
3272
3338
|
}
|
|
3273
3339
|
|
|
3274
3340
|
// src/providers/gemini/result-formatter.ts
|
|
3275
|
-
import
|
|
3341
|
+
import fs19 from "fs";
|
|
3276
3342
|
import path23 from "path";
|
|
3277
3343
|
function formatResults(results, format, outputPath) {
|
|
3278
3344
|
const content = renderFormat(results, format);
|
|
3279
3345
|
if (outputPath) {
|
|
3280
|
-
|
|
3281
|
-
|
|
3346
|
+
fs19.mkdirSync(path23.dirname(path23.resolve(outputPath)), { recursive: true });
|
|
3347
|
+
fs19.writeFileSync(outputPath, content, "utf8");
|
|
3282
3348
|
} else {
|
|
3283
3349
|
process.stdout.write(content);
|
|
3284
3350
|
}
|
|
@@ -3345,7 +3411,7 @@ function registerAnalyzeCommand(parent) {
|
|
|
3345
3411
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3346
3412
|
const model = opts.model ?? getDefaultModel("analyze");
|
|
3347
3413
|
for (const f of opts.files) {
|
|
3348
|
-
if (!
|
|
3414
|
+
if (!fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
3349
3415
|
}
|
|
3350
3416
|
const results = [];
|
|
3351
3417
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -3391,14 +3457,14 @@ function registerAnalyzeCommand(parent) {
|
|
|
3391
3457
|
|
|
3392
3458
|
// src/providers/gemini/commands/extract.ts
|
|
3393
3459
|
init_errors();
|
|
3394
|
-
import
|
|
3460
|
+
import fs21 from "fs";
|
|
3395
3461
|
function registerExtractCommand(parent) {
|
|
3396
3462
|
parent.command("extract").description("Extract structured information from files with Gemini").requiredOption("--files <paths...>", "Input file paths").requiredOption("--prompt <text>", "Extraction prompt (required)").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
3397
3463
|
async (opts) => {
|
|
3398
3464
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3399
3465
|
const model = opts.model ?? getDefaultModel("extract");
|
|
3400
3466
|
for (const f of opts.files) {
|
|
3401
|
-
if (!
|
|
3467
|
+
if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
3402
3468
|
}
|
|
3403
3469
|
const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
|
|
3404
3470
|
const results = [];
|
|
@@ -3440,7 +3506,7 @@ function registerExtractCommand(parent) {
|
|
|
3440
3506
|
init_errors();
|
|
3441
3507
|
|
|
3442
3508
|
// src/providers/gemini/generators/speech.ts
|
|
3443
|
-
import
|
|
3509
|
+
import fs22 from "fs";
|
|
3444
3510
|
import path24 from "path";
|
|
3445
3511
|
|
|
3446
3512
|
// src/providers/gemini/voices.ts
|
|
@@ -3605,11 +3671,11 @@ function extractWavData(wav) {
|
|
|
3605
3671
|
function saveSpeech(outBytes, outputFormat, output, logger) {
|
|
3606
3672
|
const outDir = getOutputDir();
|
|
3607
3673
|
const dest = path24.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
3608
|
-
|
|
3674
|
+
fs22.writeFileSync(dest, outBytes);
|
|
3609
3675
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
3610
3676
|
if (output) {
|
|
3611
|
-
|
|
3612
|
-
|
|
3677
|
+
fs22.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
|
|
3678
|
+
fs22.copyFileSync(dest, output);
|
|
3613
3679
|
logger?.success(`Copied to: ${output}`);
|
|
3614
3680
|
}
|
|
3615
3681
|
return dest;
|
|
@@ -3721,11 +3787,179 @@ function collectSpeakers(value, prev) {
|
|
|
3721
3787
|
}
|
|
3722
3788
|
|
|
3723
3789
|
// src/providers/gemini/commands/generate-video.ts
|
|
3724
|
-
import
|
|
3790
|
+
import fs24 from "fs";
|
|
3725
3791
|
import path25 from "path";
|
|
3726
3792
|
init_errors();
|
|
3727
|
-
|
|
3728
|
-
|
|
3793
|
+
|
|
3794
|
+
// src/providers/gemini/veo.ts
|
|
3795
|
+
import fs23 from "fs";
|
|
3796
|
+
|
|
3797
|
+
// src/core/async-poll.ts
|
|
3798
|
+
init_errors();
|
|
3799
|
+
var PollTimeoutError2 = class extends MultixError {
|
|
3800
|
+
constructor(message, lastStatus) {
|
|
3801
|
+
super(message, "POLL_TIMEOUT");
|
|
3802
|
+
this.lastStatus = lastStatus;
|
|
3803
|
+
this.name = "PollTimeoutError";
|
|
3804
|
+
}
|
|
3805
|
+
lastStatus;
|
|
3806
|
+
};
|
|
3807
|
+
function isTransientHttpError(error) {
|
|
3808
|
+
if (!(error instanceof HttpError)) return false;
|
|
3809
|
+
return error.status === 0 || error.status === 429 || error.status >= 500;
|
|
3810
|
+
}
|
|
3811
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3812
|
+
async function pollUntil2(opts) {
|
|
3813
|
+
const now = opts.now ?? Date.now;
|
|
3814
|
+
const sleep5 = opts.sleep ?? defaultSleep;
|
|
3815
|
+
const random = opts.random ?? Math.random;
|
|
3816
|
+
const isTransient = opts.isTransientError ?? isTransientHttpError;
|
|
3817
|
+
const maxInterval = opts.maxIntervalMs ?? 15e3;
|
|
3818
|
+
const minInterval = opts.minIntervalMs ?? 500;
|
|
3819
|
+
const factor = opts.backoffFactor ?? 1.5;
|
|
3820
|
+
const jitter = opts.jitter ?? true;
|
|
3821
|
+
const deadline = now() + opts.timeoutMs;
|
|
3822
|
+
let interval = opts.intervalMs ?? 3e3;
|
|
3823
|
+
let attempt = 0;
|
|
3824
|
+
let last;
|
|
3825
|
+
let lastError;
|
|
3826
|
+
for (; ; ) {
|
|
3827
|
+
attempt++;
|
|
3828
|
+
let retryAfterMs;
|
|
3829
|
+
try {
|
|
3830
|
+
last = await opts.fetchStatus({ remainingMs: Math.max(1, deadline - now()), attempt });
|
|
3831
|
+
lastError = void 0;
|
|
3832
|
+
opts.onPoll?.(last, attempt);
|
|
3833
|
+
if (opts.isTerminal(last)) return last;
|
|
3834
|
+
} catch (error) {
|
|
3835
|
+
if (!isTransient(error)) throw error;
|
|
3836
|
+
lastError = error;
|
|
3837
|
+
if (error instanceof HttpError) retryAfterMs = error.retryAfterMs;
|
|
3838
|
+
}
|
|
3839
|
+
const remaining = deadline - now();
|
|
3840
|
+
if (remaining <= 0) {
|
|
3841
|
+
const hint = opts.resumeHint ? ` ${opts.resumeHint}` : "";
|
|
3842
|
+
const cause = lastError instanceof Error ? ` Last error: ${lastError.message}.` : "";
|
|
3843
|
+
throw new PollTimeoutError2(
|
|
3844
|
+
`Timed out after ${opts.timeoutMs}ms waiting for job.${cause}${hint}`,
|
|
3845
|
+
last
|
|
3846
|
+
);
|
|
3847
|
+
}
|
|
3848
|
+
let delay = retryAfterMs ?? interval;
|
|
3849
|
+
if (jitter && retryAfterMs === void 0) delay *= 0.8 + random() * 0.4;
|
|
3850
|
+
await sleep5(Math.min(Math.max(delay, minInterval), remaining));
|
|
3851
|
+
interval = Math.min(interval * factor, maxInterval);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
// src/providers/gemini/veo.ts
|
|
3856
|
+
init_errors();
|
|
3857
|
+
var API_HOST = "generativelanguage.googleapis.com";
|
|
3858
|
+
var BASE2 = `https://${API_HOST}/v1beta`;
|
|
3859
|
+
var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
3860
|
+
var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3861
|
+
function veoImageFromFile(imgPath) {
|
|
3862
|
+
if (!fs23.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
|
|
3863
|
+
const detected = getMimeType(imgPath);
|
|
3864
|
+
const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
|
|
3865
|
+
if (!VEO_IMAGE_TYPES.has(mimeType)) {
|
|
3866
|
+
throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
|
|
3867
|
+
}
|
|
3868
|
+
return { bytesBase64Encoded: fs23.readFileSync(imgPath).toString("base64"), mimeType };
|
|
3869
|
+
}
|
|
3870
|
+
function veoFramesFromPaths(paths = []) {
|
|
3871
|
+
if (paths.length > 2) {
|
|
3872
|
+
throw new ValidationError(
|
|
3873
|
+
"--reference-images accepts at most two images (opening frame, closing frame)"
|
|
3874
|
+
);
|
|
3875
|
+
}
|
|
3876
|
+
const [first, last] = paths;
|
|
3877
|
+
return {
|
|
3878
|
+
image: first ? veoImageFromFile(first) : void 0,
|
|
3879
|
+
lastFrame: last ? veoImageFromFile(last) : void 0
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
function modelPath(model) {
|
|
3883
|
+
return model.replace(/^models\//, "");
|
|
3884
|
+
}
|
|
3885
|
+
function buildVeoRequest(opts) {
|
|
3886
|
+
const instance = { prompt: opts.prompt };
|
|
3887
|
+
if (opts.image) instance.image = opts.image;
|
|
3888
|
+
if (opts.lastFrame) instance.lastFrame = opts.lastFrame;
|
|
3889
|
+
return {
|
|
3890
|
+
instances: [instance],
|
|
3891
|
+
parameters: { aspectRatio: opts.aspectRatio, resolution: opts.resolution }
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
function extractVeoVideoUri(op) {
|
|
3895
|
+
const r = op.response ?? {};
|
|
3896
|
+
return r.generateVideoResponse?.generatedSamples?.[0]?.video?.uri ?? r.generatedSamples?.[0]?.video?.uri ?? r.generatedVideos?.[0]?.video?.uri;
|
|
3897
|
+
}
|
|
3898
|
+
function headers(apiKey) {
|
|
3899
|
+
return { "x-goog-api-key": apiKey };
|
|
3900
|
+
}
|
|
3901
|
+
async function submitVeo(apiKey, model, body) {
|
|
3902
|
+
try {
|
|
3903
|
+
return await httpJson({
|
|
3904
|
+
url: `${BASE2}/models/${modelPath(model)}:predictLongRunning`,
|
|
3905
|
+
method: "POST",
|
|
3906
|
+
headers: headers(apiKey),
|
|
3907
|
+
body
|
|
3908
|
+
});
|
|
3909
|
+
} catch (e) {
|
|
3910
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3911
|
+
throw new ProviderError(`Veo submit failed: ${msg}`, "gemini");
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
async function waitForVeo(apiKey, operation, logger, opts = {}) {
|
|
3915
|
+
if (operation.done) return checkVeoResult(operation);
|
|
3916
|
+
const finalOp = await pollUntil2({
|
|
3917
|
+
fetchStatus: ({ remainingMs }) => httpJson({
|
|
3918
|
+
url: `${BASE2}/${operation.name}`,
|
|
3919
|
+
headers: headers(apiKey),
|
|
3920
|
+
timeoutMs: Math.min(remainingMs, 6e4)
|
|
3921
|
+
}),
|
|
3922
|
+
isTerminal: (op) => op.done === true,
|
|
3923
|
+
timeoutMs: opts.timeoutMs ?? VEO_TIMEOUT_MS,
|
|
3924
|
+
intervalMs: opts.intervalMs ?? 1e4,
|
|
3925
|
+
maxIntervalMs: 2e4,
|
|
3926
|
+
sleep: opts.sleep,
|
|
3927
|
+
onPoll: (op, attempt) => {
|
|
3928
|
+
if (!op.done) logger.debug(`Still generating... (poll ${attempt})`);
|
|
3929
|
+
},
|
|
3930
|
+
resumeHint: `Operation (still running server-side): ${operation.name}`
|
|
3931
|
+
});
|
|
3932
|
+
return checkVeoResult(finalOp);
|
|
3933
|
+
}
|
|
3934
|
+
function checkVeoResult(op) {
|
|
3935
|
+
if (op.error) throw new ProviderError(`Veo error: ${JSON.stringify(op.error)}`, "gemini");
|
|
3936
|
+
return op;
|
|
3937
|
+
}
|
|
3938
|
+
async function downloadVeoVideo(apiKey, uri, dest) {
|
|
3939
|
+
let host = "";
|
|
3940
|
+
try {
|
|
3941
|
+
host = new URL(uri).hostname;
|
|
3942
|
+
} catch {
|
|
3943
|
+
throw new ProviderError("Veo returned an invalid video URI", "gemini");
|
|
3944
|
+
}
|
|
3945
|
+
await downloadFile(uri, dest, 3e5, host === API_HOST ? headers(apiKey) : void 0);
|
|
3946
|
+
}
|
|
3947
|
+
async function runVeo(apiKey, model, request, dest, logger) {
|
|
3948
|
+
const operation = await submitVeo(apiKey, model, buildVeoRequest(request));
|
|
3949
|
+
logger.debug(`Operation: ${operation.name}`);
|
|
3950
|
+
const finalOp = await waitForVeo(apiKey, operation, logger);
|
|
3951
|
+
const uri = extractVeoVideoUri(finalOp);
|
|
3952
|
+
if (!uri) {
|
|
3953
|
+
throw new ProviderError(
|
|
3954
|
+
`Unexpected Veo response structure: ${JSON.stringify(finalOp.response)}`,
|
|
3955
|
+
"gemini"
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
await downloadVeoVideo(apiKey, uri, dest);
|
|
3959
|
+
return finalOp;
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
// src/providers/gemini/commands/generate-video.ts
|
|
3729
3963
|
function registerGenerateVideoCommand(parent) {
|
|
3730
3964
|
parent.command("generate-video").description("Generate video with Veo via Gemini API [EXPERIMENTAL]").requiredOption("--prompt <text>", "Video generation prompt").option("--model <id>", "Veo model to use").option("--resolution <res>", "Video resolution: 720p|1080p", "1080p").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option(
|
|
3731
3965
|
"--reference-images <paths...>",
|
|
@@ -3749,72 +3983,24 @@ function registerGenerateVideoCommand(parent) {
|
|
|
3749
3983
|
}
|
|
3750
3984
|
logger.debug(`Submitting video generation with ${model}...`);
|
|
3751
3985
|
logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
|
|
3752
|
-
const
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
}
|
|
3767
|
-
const headers = { "x-goog-api-key": apiKey, "Content-Type": "application/json" };
|
|
3768
|
-
let operation;
|
|
3769
|
-
try {
|
|
3770
|
-
operation = await httpJson({
|
|
3771
|
-
url: `${BASE2}/v1beta/models/${model}:generateVideos`,
|
|
3772
|
-
method: "POST",
|
|
3773
|
-
headers,
|
|
3774
|
-
body: {
|
|
3775
|
-
contents: [{ parts: contentParts }],
|
|
3776
|
-
generationConfig: config
|
|
3777
|
-
}
|
|
3778
|
-
});
|
|
3779
|
-
} catch (e) {
|
|
3780
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
3781
|
-
throw new ProviderError(`Video generation submit failed: ${msg}`, "gemini");
|
|
3782
|
-
}
|
|
3783
|
-
const operationName = operation.name;
|
|
3784
|
-
logger.debug(`Operation: ${operationName}`);
|
|
3785
|
-
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
3786
|
-
let done = false;
|
|
3787
|
-
let finalOp = operation;
|
|
3788
|
-
while (!done && Date.now() < deadline) {
|
|
3789
|
-
await sleep3(1e4);
|
|
3790
|
-
finalOp = await httpJson({
|
|
3791
|
-
url: `${BASE2}/v1beta/${operationName}`,
|
|
3792
|
-
headers
|
|
3793
|
-
});
|
|
3794
|
-
done = finalOp.done === true;
|
|
3795
|
-
if (!done)
|
|
3796
|
-
logger.debug(
|
|
3797
|
-
`Still generating... (${Math.round((deadline - Date.now()) / 1e3)}s left)`
|
|
3798
|
-
);
|
|
3799
|
-
}
|
|
3800
|
-
if (!done) throw new ProviderError("Video generation timed out after 10 minutes", "gemini");
|
|
3801
|
-
if (finalOp.error)
|
|
3802
|
-
throw new ProviderError(`Veo error: ${JSON.stringify(finalOp.error)}`, "gemini");
|
|
3803
|
-
const videoUri = finalOp.response?.generatedSamples?.[0]?.video?.uri ?? finalOp.response?.generatedVideos?.[0]?.video?.uri;
|
|
3804
|
-
if (!videoUri) {
|
|
3805
|
-
throw new ProviderError(
|
|
3806
|
-
`Unexpected Veo response structure: ${JSON.stringify(finalOp.response)}`,
|
|
3807
|
-
"gemini"
|
|
3808
|
-
);
|
|
3809
|
-
}
|
|
3810
|
-
const outDir = getOutputDir();
|
|
3811
|
-
const dest = path25.join(outDir, `veo_generated_${Date.now()}.mp4`);
|
|
3812
|
-
const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
|
|
3813
|
-
await downloadFile2(videoUri, dest);
|
|
3986
|
+
const frames = veoFramesFromPaths(opts.referenceImages);
|
|
3987
|
+
const dest = path25.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
|
|
3988
|
+
const finalOp = await runVeo(
|
|
3989
|
+
apiKey,
|
|
3990
|
+
model,
|
|
3991
|
+
{
|
|
3992
|
+
prompt: opts.prompt,
|
|
3993
|
+
...frames,
|
|
3994
|
+
aspectRatio: opts.aspectRatio,
|
|
3995
|
+
resolution: opts.resolution
|
|
3996
|
+
},
|
|
3997
|
+
dest,
|
|
3998
|
+
logger
|
|
3999
|
+
);
|
|
3814
4000
|
logger.success(`Saved video: ${dest}`);
|
|
3815
4001
|
if (opts.output) {
|
|
3816
|
-
|
|
3817
|
-
|
|
4002
|
+
fs24.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
|
|
4003
|
+
fs24.copyFileSync(dest, opts.output);
|
|
3818
4004
|
logger.success(`Copied to: ${opts.output}`);
|
|
3819
4005
|
}
|
|
3820
4006
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -3827,13 +4013,10 @@ Generated video: ${dest}`);
|
|
|
3827
4013
|
}
|
|
3828
4014
|
);
|
|
3829
4015
|
}
|
|
3830
|
-
function sleep3(ms) {
|
|
3831
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
3832
|
-
}
|
|
3833
4016
|
|
|
3834
4017
|
// src/providers/gemini/commands/generate.ts
|
|
3835
4018
|
init_errors();
|
|
3836
|
-
import
|
|
4019
|
+
import fs25 from "fs";
|
|
3837
4020
|
import path26 from "path";
|
|
3838
4021
|
function registerGenerateCommand(parent) {
|
|
3839
4022
|
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(
|
|
@@ -3877,7 +4060,7 @@ function registerGenerateCommand(parent) {
|
|
|
3877
4060
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
3878
4061
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
3879
4062
|
const dest = path26.join(outDir, fname);
|
|
3880
|
-
|
|
4063
|
+
fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
3881
4064
|
savedFiles.push(dest);
|
|
3882
4065
|
logger.success(`Saved: ${dest}`);
|
|
3883
4066
|
}
|
|
@@ -3892,8 +4075,8 @@ function registerGenerateCommand(parent) {
|
|
|
3892
4075
|
process.exit(1);
|
|
3893
4076
|
}
|
|
3894
4077
|
if (opts.output && savedFiles[0]) {
|
|
3895
|
-
|
|
3896
|
-
|
|
4078
|
+
fs25.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
|
|
4079
|
+
fs25.copyFileSync(savedFiles[0], opts.output);
|
|
3897
4080
|
logger.success(`Copied to: ${opts.output}`);
|
|
3898
4081
|
}
|
|
3899
4082
|
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
@@ -3905,7 +4088,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
3905
4088
|
}
|
|
3906
4089
|
|
|
3907
4090
|
// src/providers/gemini/commands/image-to-image.ts
|
|
3908
|
-
import
|
|
4091
|
+
import fs26 from "fs";
|
|
3909
4092
|
import path27 from "path";
|
|
3910
4093
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
3911
4094
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
@@ -3968,13 +4151,13 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
3968
4151
|
if (!img) continue;
|
|
3969
4152
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
3970
4153
|
const dest = path27.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
3971
|
-
|
|
4154
|
+
fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
3972
4155
|
saved.push(dest);
|
|
3973
4156
|
logger.success(`Saved: ${dest}`);
|
|
3974
4157
|
}
|
|
3975
4158
|
if (opts.output && saved[0]) {
|
|
3976
|
-
|
|
3977
|
-
|
|
4159
|
+
fs26.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
|
|
4160
|
+
fs26.copyFileSync(saved[0], opts.output);
|
|
3978
4161
|
logger.success(`Copied to: ${opts.output}`);
|
|
3979
4162
|
}
|
|
3980
4163
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4010,23 +4193,9 @@ function sniffImageMime(buf, url) {
|
|
|
4010
4193
|
}
|
|
4011
4194
|
|
|
4012
4195
|
// src/providers/gemini/commands/image-to-video.ts
|
|
4013
|
-
import
|
|
4196
|
+
import fs27 from "fs";
|
|
4014
4197
|
import path28 from "path";
|
|
4015
4198
|
init_errors();
|
|
4016
|
-
init_http_client();
|
|
4017
|
-
var BASE3 = "https://generativelanguage.googleapis.com";
|
|
4018
|
-
function imagePart(imgPath) {
|
|
4019
|
-
if (!fs25.existsSync(imgPath)) {
|
|
4020
|
-
throw new ValidationError(`Image not found: ${imgPath}`);
|
|
4021
|
-
}
|
|
4022
|
-
const data = fs25.readFileSync(imgPath).toString("base64");
|
|
4023
|
-
const ext = path28.extname(imgPath).toLowerCase();
|
|
4024
|
-
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
4025
|
-
return { inlineData: { mimeType, data } };
|
|
4026
|
-
}
|
|
4027
|
-
function sleep4(ms) {
|
|
4028
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
4029
|
-
}
|
|
4030
4199
|
function registerGeminiImageToVideoCommand(parent) {
|
|
4031
4200
|
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(
|
|
4032
4201
|
async (imagePath, opts) => {
|
|
@@ -4045,60 +4214,27 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
4045
4214
|
if (!ASPECT_RATIOS.includes(opts.aspectRatio)) {
|
|
4046
4215
|
throw new ValidationError(`Invalid aspect ratio: ${opts.aspectRatio}`);
|
|
4047
4216
|
}
|
|
4048
|
-
const contentParts = [
|
|
4049
|
-
{ text: opts.prompt },
|
|
4050
|
-
imagePart(imagePath)
|
|
4051
|
-
];
|
|
4052
|
-
if (opts.lastFrame) contentParts.push(imagePart(opts.lastFrame));
|
|
4053
|
-
const headers = { "x-goog-api-key": apiKey, "Content-Type": "application/json" };
|
|
4054
4217
|
logger.info(
|
|
4055
4218
|
"Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
|
|
4056
4219
|
);
|
|
4057
|
-
let operation;
|
|
4058
|
-
try {
|
|
4059
|
-
operation = await httpJson({
|
|
4060
|
-
url: `${BASE3}/v1beta/models/${model}:generateVideos`,
|
|
4061
|
-
method: "POST",
|
|
4062
|
-
headers,
|
|
4063
|
-
body: {
|
|
4064
|
-
contents: [{ parts: contentParts }],
|
|
4065
|
-
generationConfig: { aspectRatio: opts.aspectRatio, resolution: opts.resolution }
|
|
4066
|
-
}
|
|
4067
|
-
});
|
|
4068
|
-
} catch (e) {
|
|
4069
|
-
throw new ProviderError(
|
|
4070
|
-
`Submit failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
4071
|
-
"gemini"
|
|
4072
|
-
);
|
|
4073
|
-
}
|
|
4074
|
-
const operationName = operation.name;
|
|
4075
|
-
logger.debug(`Operation: ${operationName}`);
|
|
4076
|
-
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
4077
|
-
let finalOp = operation;
|
|
4078
|
-
while (!finalOp.done && Date.now() < deadline) {
|
|
4079
|
-
await sleep4(1e4);
|
|
4080
|
-
finalOp = await httpJson({ url: `${BASE3}/v1beta/${operationName}`, headers });
|
|
4081
|
-
if (!finalOp.done) {
|
|
4082
|
-
logger.debug(`Still generating... ${Math.round((deadline - Date.now()) / 1e3)}s left`);
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
if (!finalOp.done) throw new ProviderError("Veo timed out after 10 minutes", "gemini");
|
|
4086
|
-
if (finalOp.error) {
|
|
4087
|
-
throw new ProviderError(`Veo error: ${JSON.stringify(finalOp.error)}`, "gemini");
|
|
4088
|
-
}
|
|
4089
|
-
const videoUri = finalOp.response?.generatedSamples?.[0]?.video?.uri ?? finalOp.response?.generatedVideos?.[0]?.video?.uri;
|
|
4090
|
-
if (!videoUri) {
|
|
4091
|
-
throw new ProviderError(
|
|
4092
|
-
`Unexpected Veo response: ${JSON.stringify(finalOp.response)}`,
|
|
4093
|
-
"gemini"
|
|
4094
|
-
);
|
|
4095
|
-
}
|
|
4096
4220
|
const dest = path28.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
4097
|
-
await
|
|
4221
|
+
const finalOp = await runVeo(
|
|
4222
|
+
apiKey,
|
|
4223
|
+
model,
|
|
4224
|
+
{
|
|
4225
|
+
prompt: opts.prompt,
|
|
4226
|
+
image: veoImageFromFile(imagePath),
|
|
4227
|
+
lastFrame: opts.lastFrame ? veoImageFromFile(opts.lastFrame) : void 0,
|
|
4228
|
+
aspectRatio: opts.aspectRatio,
|
|
4229
|
+
resolution: opts.resolution
|
|
4230
|
+
},
|
|
4231
|
+
dest,
|
|
4232
|
+
logger
|
|
4233
|
+
);
|
|
4098
4234
|
logger.success(`Saved video: ${dest}`);
|
|
4099
4235
|
if (opts.output) {
|
|
4100
|
-
|
|
4101
|
-
|
|
4236
|
+
fs27.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
|
|
4237
|
+
fs27.copyFileSync(dest, opts.output);
|
|
4102
4238
|
logger.success(`Copied to: ${opts.output}`);
|
|
4103
4239
|
}
|
|
4104
4240
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -4114,14 +4250,14 @@ Generated video: ${dest}`);
|
|
|
4114
4250
|
|
|
4115
4251
|
// src/providers/gemini/commands/transcribe.ts
|
|
4116
4252
|
init_errors();
|
|
4117
|
-
import
|
|
4253
|
+
import fs28 from "fs";
|
|
4118
4254
|
function registerTranscribeCommand2(parent) {
|
|
4119
4255
|
parent.command("transcribe").description("Transcribe audio or video files with Gemini").requiredOption("--files <paths...>", "Audio/video file paths to transcribe").option("--prompt <text>", "Transcription prompt", "Generate a transcript with timestamps").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
4120
4256
|
async (opts) => {
|
|
4121
4257
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4122
4258
|
const model = opts.model ?? getDefaultModel("transcribe");
|
|
4123
4259
|
for (const f of opts.files) {
|
|
4124
|
-
if (!
|
|
4260
|
+
if (!fs28.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
4125
4261
|
}
|
|
4126
4262
|
const results = [];
|
|
4127
4263
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -4178,13 +4314,11 @@ function registerGeminiCommands(program2) {
|
|
|
4178
4314
|
}
|
|
4179
4315
|
|
|
4180
4316
|
// src/providers/leonardo/commands/generate.ts
|
|
4181
|
-
|
|
4182
|
-
import fs27 from "fs";
|
|
4317
|
+
import fs29 from "fs";
|
|
4183
4318
|
import path29 from "path";
|
|
4184
4319
|
|
|
4185
4320
|
// src/providers/leonardo/client.ts
|
|
4186
4321
|
init_errors();
|
|
4187
|
-
init_http_client();
|
|
4188
4322
|
|
|
4189
4323
|
// src/providers/leonardo/models.ts
|
|
4190
4324
|
var DEFAULT_LEONARDO_BASE_URL = "https://cloud.leonardo.ai/api/rest/v1";
|
|
@@ -4391,8 +4525,8 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
4391
4525
|
}
|
|
4392
4526
|
if (opts.output && saved[0]) {
|
|
4393
4527
|
const dest = path29.resolve(opts.output);
|
|
4394
|
-
|
|
4395
|
-
|
|
4528
|
+
fs29.mkdirSync(path29.dirname(dest), { recursive: true });
|
|
4529
|
+
fs29.copyFileSync(saved[0], dest);
|
|
4396
4530
|
logger.success(`Copied to ${dest}`);
|
|
4397
4531
|
}
|
|
4398
4532
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4404,8 +4538,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4404
4538
|
}
|
|
4405
4539
|
|
|
4406
4540
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
4407
|
-
|
|
4408
|
-
import fs28 from "fs";
|
|
4541
|
+
import fs30 from "fs";
|
|
4409
4542
|
import path30 from "path";
|
|
4410
4543
|
function extFromUrl3(url, fallback = ".png") {
|
|
4411
4544
|
try {
|
|
@@ -4482,8 +4615,8 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
4482
4615
|
}
|
|
4483
4616
|
if (opts.output && saved[0]) {
|
|
4484
4617
|
const dest = path30.resolve(opts.output);
|
|
4485
|
-
|
|
4486
|
-
|
|
4618
|
+
fs30.mkdirSync(path30.dirname(dest), { recursive: true });
|
|
4619
|
+
fs30.copyFileSync(saved[0], dest);
|
|
4487
4620
|
logger.success(`Copied to ${dest}`);
|
|
4488
4621
|
}
|
|
4489
4622
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4495,7 +4628,6 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4495
4628
|
}
|
|
4496
4629
|
|
|
4497
4630
|
// src/providers/leonardo/commands/video.ts
|
|
4498
|
-
init_http_client();
|
|
4499
4631
|
import path31 from "path";
|
|
4500
4632
|
function v2DimsFor(resolution) {
|
|
4501
4633
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
@@ -4713,7 +4845,6 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
4713
4845
|
}
|
|
4714
4846
|
|
|
4715
4847
|
// src/providers/leonardo/commands/status.ts
|
|
4716
|
-
init_http_client();
|
|
4717
4848
|
import path32 from "path";
|
|
4718
4849
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
4719
4850
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
@@ -4871,7 +5002,6 @@ init_errors();
|
|
|
4871
5002
|
|
|
4872
5003
|
// src/providers/minimax/client.ts
|
|
4873
5004
|
init_errors();
|
|
4874
|
-
init_http_client();
|
|
4875
5005
|
var MINIMAX_BASE_URL = "https://api.minimax.io/v1";
|
|
4876
5006
|
function requireMinimaxKey() {
|
|
4877
5007
|
const key = resolveKey("MINIMAX_API_KEY");
|
|
@@ -4926,7 +5056,7 @@ async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
|
|
|
4926
5056
|
if (status === "Failed" || status === "Error") {
|
|
4927
5057
|
throw new ProviderError(`Task ${taskId} failed: ${JSON.stringify(result)}`, "minimax");
|
|
4928
5058
|
}
|
|
4929
|
-
await
|
|
5059
|
+
await sleep3(intervalMs);
|
|
4930
5060
|
elapsed += intervalMs;
|
|
4931
5061
|
}
|
|
4932
5062
|
throw new ProviderError(`Task ${taskId} timed out after ${timeoutMs / 1e3}s`, "minimax");
|
|
@@ -4945,13 +5075,12 @@ async function downloadMinimaxFile(fileId, apiKey, dest, logger) {
|
|
|
4945
5075
|
await downloadFile(downloadUrl, dest);
|
|
4946
5076
|
return dest;
|
|
4947
5077
|
}
|
|
4948
|
-
function
|
|
5078
|
+
function sleep3(ms) {
|
|
4949
5079
|
return new Promise((r) => setTimeout(r, ms));
|
|
4950
5080
|
}
|
|
4951
5081
|
|
|
4952
5082
|
// src/providers/minimax/generators/music.ts
|
|
4953
|
-
|
|
4954
|
-
import fs29 from "fs";
|
|
5083
|
+
import fs31 from "fs";
|
|
4955
5084
|
import path33 from "path";
|
|
4956
5085
|
async function generateMinimaxMusic(opts) {
|
|
4957
5086
|
const {
|
|
@@ -5001,10 +5130,10 @@ async function generateMinimaxMusic(opts) {
|
|
|
5001
5130
|
try {
|
|
5002
5131
|
if (audioData.startsWith("http")) {
|
|
5003
5132
|
const bytes = await fetchBytes(audioData);
|
|
5004
|
-
|
|
5133
|
+
fs31.writeFileSync(dest, bytes);
|
|
5005
5134
|
} else {
|
|
5006
5135
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
5007
|
-
|
|
5136
|
+
fs31.writeFileSync(dest, audioBytes);
|
|
5008
5137
|
}
|
|
5009
5138
|
} catch (e) {
|
|
5010
5139
|
return {
|
|
@@ -5015,8 +5144,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
5015
5144
|
const durSec = durationMs / 1e3;
|
|
5016
5145
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
5017
5146
|
if (output) {
|
|
5018
|
-
|
|
5019
|
-
|
|
5147
|
+
fs31.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
|
|
5148
|
+
fs31.copyFileSync(dest, output);
|
|
5020
5149
|
}
|
|
5021
5150
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
5022
5151
|
}
|
|
@@ -5090,7 +5219,7 @@ Generated music: ${result.generatedAudio}`);
|
|
|
5090
5219
|
init_errors();
|
|
5091
5220
|
|
|
5092
5221
|
// src/providers/minimax/generators/speech.ts
|
|
5093
|
-
import
|
|
5222
|
+
import fs32 from "fs";
|
|
5094
5223
|
import path34 from "path";
|
|
5095
5224
|
async function generateMinimaxSpeech(opts) {
|
|
5096
5225
|
const {
|
|
@@ -5139,11 +5268,11 @@ async function generateMinimaxSpeech(opts) {
|
|
|
5139
5268
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
5140
5269
|
const outDir = getOutputDir();
|
|
5141
5270
|
const dest = path34.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
5142
|
-
|
|
5271
|
+
fs32.writeFileSync(dest, audioBytes);
|
|
5143
5272
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
5144
5273
|
if (output) {
|
|
5145
|
-
|
|
5146
|
-
|
|
5274
|
+
fs32.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
|
|
5275
|
+
fs32.copyFileSync(dest, output);
|
|
5147
5276
|
}
|
|
5148
5277
|
return { status: "success", generatedAudio: dest, model };
|
|
5149
5278
|
}
|
|
@@ -5190,7 +5319,7 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
5190
5319
|
init_errors();
|
|
5191
5320
|
|
|
5192
5321
|
// src/providers/minimax/generators/video.ts
|
|
5193
|
-
import
|
|
5322
|
+
import fs33 from "fs";
|
|
5194
5323
|
import path35 from "path";
|
|
5195
5324
|
async function generateMinimaxVideo(opts) {
|
|
5196
5325
|
const {
|
|
@@ -5244,11 +5373,11 @@ async function generateMinimaxVideo(opts) {
|
|
|
5244
5373
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
5245
5374
|
}
|
|
5246
5375
|
const elapsed = (Date.now() - start) / 1e3;
|
|
5247
|
-
const stat =
|
|
5376
|
+
const stat = fs33.statSync(dest);
|
|
5248
5377
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
5249
5378
|
if (output) {
|
|
5250
|
-
|
|
5251
|
-
|
|
5379
|
+
fs33.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
|
|
5380
|
+
fs33.copyFileSync(dest, output);
|
|
5252
5381
|
}
|
|
5253
5382
|
await maybeDownloadThumb(pollResult, dest, {
|
|
5254
5383
|
skip: thumb === false,
|
|
@@ -5310,8 +5439,7 @@ Generated video: ${result.generatedVideo}`);
|
|
|
5310
5439
|
|
|
5311
5440
|
// src/providers/minimax/generators/image.ts
|
|
5312
5441
|
init_errors();
|
|
5313
|
-
|
|
5314
|
-
import fs32 from "fs";
|
|
5442
|
+
import fs34 from "fs";
|
|
5315
5443
|
import path36 from "path";
|
|
5316
5444
|
async function generateMinimaxImage(opts) {
|
|
5317
5445
|
const {
|
|
@@ -5351,7 +5479,7 @@ async function generateMinimaxImage(opts) {
|
|
|
5351
5479
|
const bytes = await fetchBytes(url);
|
|
5352
5480
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
5353
5481
|
const dest = path36.join(outDir, fname);
|
|
5354
|
-
|
|
5482
|
+
fs34.writeFileSync(dest, bytes);
|
|
5355
5483
|
savedFiles.push(dest);
|
|
5356
5484
|
logger?.success(`Saved: ${dest}`);
|
|
5357
5485
|
} catch (e) {
|
|
@@ -5362,8 +5490,8 @@ async function generateMinimaxImage(opts) {
|
|
|
5362
5490
|
}
|
|
5363
5491
|
}
|
|
5364
5492
|
if (output && savedFiles[0]) {
|
|
5365
|
-
|
|
5366
|
-
|
|
5493
|
+
fs34.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
|
|
5494
|
+
fs34.copyFileSync(savedFiles[0], output);
|
|
5367
5495
|
}
|
|
5368
5496
|
return { status: "success", generatedImages: savedFiles, model };
|
|
5369
5497
|
}
|
|
@@ -5402,8 +5530,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
5402
5530
|
|
|
5403
5531
|
// src/providers/minimax/commands/image-to-image.ts
|
|
5404
5532
|
init_errors();
|
|
5405
|
-
|
|
5406
|
-
import fs33 from "fs";
|
|
5533
|
+
import fs35 from "fs";
|
|
5407
5534
|
import path37 from "path";
|
|
5408
5535
|
function registerMinimaxImageToImageCommand(parent) {
|
|
5409
5536
|
parent.command("image-to-image").alias("i2i").description(
|
|
@@ -5460,7 +5587,7 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5460
5587
|
try {
|
|
5461
5588
|
const bytes = await fetchBytes(url);
|
|
5462
5589
|
const dest = path37.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
5463
|
-
|
|
5590
|
+
fs35.writeFileSync(dest, bytes);
|
|
5464
5591
|
saved.push(dest);
|
|
5465
5592
|
logger.success(`Saved: ${dest}`);
|
|
5466
5593
|
} catch (e) {
|
|
@@ -5471,8 +5598,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5471
5598
|
}
|
|
5472
5599
|
}
|
|
5473
5600
|
if (opts.output && saved[0]) {
|
|
5474
|
-
|
|
5475
|
-
|
|
5601
|
+
fs35.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
|
|
5602
|
+
fs35.copyFileSync(saved[0], opts.output);
|
|
5476
5603
|
logger.success(`Copied to: ${opts.output}`);
|
|
5477
5604
|
}
|
|
5478
5605
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -5606,15 +5733,13 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
5606
5733
|
}
|
|
5607
5734
|
|
|
5608
5735
|
// src/providers/openai/openai-audio.ts
|
|
5609
|
-
|
|
5610
|
-
import fs35 from "fs";
|
|
5736
|
+
import fs37 from "fs";
|
|
5611
5737
|
import path39 from "path";
|
|
5612
5738
|
|
|
5613
5739
|
// src/providers/openai/client.ts
|
|
5614
|
-
import
|
|
5740
|
+
import fs36 from "fs";
|
|
5615
5741
|
import path38 from "path";
|
|
5616
5742
|
init_errors();
|
|
5617
|
-
init_http_client();
|
|
5618
5743
|
var DEFAULT_TIMEOUT_MS3 = 24e4;
|
|
5619
5744
|
function requireOpenAIKey() {
|
|
5620
5745
|
const key = resolveKey("OPENAI_API_KEY");
|
|
@@ -5680,8 +5805,8 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
5680
5805
|
}
|
|
5681
5806
|
function fileFromPath(filePath) {
|
|
5682
5807
|
const abs = path38.resolve(filePath);
|
|
5683
|
-
if (!
|
|
5684
|
-
const bytes =
|
|
5808
|
+
if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
5809
|
+
const bytes = fs36.readFileSync(abs);
|
|
5685
5810
|
return new File([new Uint8Array(bytes)], path38.basename(abs));
|
|
5686
5811
|
}
|
|
5687
5812
|
|
|
@@ -5741,11 +5866,11 @@ async function generateOpenAISpeech(opts) {
|
|
|
5741
5866
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
5742
5867
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
5743
5868
|
const dest = path39.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
5744
|
-
|
|
5869
|
+
fs37.writeFileSync(dest, bytes);
|
|
5745
5870
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
5746
5871
|
if (opts.output) {
|
|
5747
|
-
|
|
5748
|
-
|
|
5872
|
+
fs37.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
|
|
5873
|
+
fs37.copyFileSync(dest, opts.output);
|
|
5749
5874
|
}
|
|
5750
5875
|
return dest;
|
|
5751
5876
|
}
|
|
@@ -5768,8 +5893,8 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
5768
5893
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
5769
5894
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
5770
5895
|
const dest = opts.output ? path39.resolve(opts.output) : path39.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
|
|
5771
|
-
|
|
5772
|
-
|
|
5896
|
+
fs37.mkdirSync(path39.dirname(dest), { recursive: true });
|
|
5897
|
+
fs37.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
|
|
5773
5898
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
5774
5899
|
return { path: dest, text };
|
|
5775
5900
|
}
|
|
@@ -5796,7 +5921,7 @@ Generated audio: ${dest}`);
|
|
|
5796
5921
|
}
|
|
5797
5922
|
|
|
5798
5923
|
// src/providers/openai/codex-image-driver.ts
|
|
5799
|
-
import
|
|
5924
|
+
import fs38 from "fs";
|
|
5800
5925
|
import os2 from "os";
|
|
5801
5926
|
import path40 from "path";
|
|
5802
5927
|
import { execa } from "execa";
|
|
@@ -5848,11 +5973,11 @@ async function runCodexImageGeneration(opts) {
|
|
|
5848
5973
|
const runner = opts.runner ?? defaultRunner;
|
|
5849
5974
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
5850
5975
|
const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
|
|
5851
|
-
|
|
5852
|
-
const tmp =
|
|
5976
|
+
fs38.mkdirSync(path40.dirname(dest), { recursive: true });
|
|
5977
|
+
const tmp = fs38.mkdtempSync(path40.join(os2.tmpdir(), "multix-codex-image-"));
|
|
5853
5978
|
const schema = path40.join(tmp, "schema.json");
|
|
5854
5979
|
const result = path40.join(tmp, "result.json");
|
|
5855
|
-
|
|
5980
|
+
fs38.writeFileSync(
|
|
5856
5981
|
schema,
|
|
5857
5982
|
JSON.stringify({
|
|
5858
5983
|
type: "object",
|
|
@@ -5882,7 +6007,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
5882
6007
|
if (res.exitCode !== 0) {
|
|
5883
6008
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
5884
6009
|
}
|
|
5885
|
-
if (!
|
|
6010
|
+
if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
|
|
5886
6011
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
5887
6012
|
}
|
|
5888
6013
|
return { path: dest };
|
|
@@ -5899,8 +6024,7 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
5899
6024
|
|
|
5900
6025
|
// src/providers/openai/openai-image.ts
|
|
5901
6026
|
init_errors();
|
|
5902
|
-
|
|
5903
|
-
import fs37 from "fs";
|
|
6027
|
+
import fs39 from "fs";
|
|
5904
6028
|
import path41 from "path";
|
|
5905
6029
|
function buildImageGenerationBody(opts) {
|
|
5906
6030
|
return {
|
|
@@ -5935,13 +6059,13 @@ function saveOpenAIImages(opts) {
|
|
|
5935
6059
|
const b64 = items[i]?.b64_json;
|
|
5936
6060
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
5937
6061
|
const dest = path41.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
5938
|
-
|
|
6062
|
+
fs39.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
5939
6063
|
saved.push(dest);
|
|
5940
6064
|
opts.logger?.success(`Saved: ${dest}`);
|
|
5941
6065
|
}
|
|
5942
6066
|
if (opts.output && saved[0]) {
|
|
5943
|
-
|
|
5944
|
-
|
|
6067
|
+
fs39.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
|
|
6068
|
+
fs39.copyFileSync(saved[0], opts.output);
|
|
5945
6069
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
5946
6070
|
}
|
|
5947
6071
|
return saved;
|
|
@@ -6139,10 +6263,9 @@ function registerOpenAICommands(program2) {
|
|
|
6139
6263
|
}
|
|
6140
6264
|
|
|
6141
6265
|
// src/providers/openrouter/client.ts
|
|
6142
|
-
import
|
|
6266
|
+
import fs40 from "fs";
|
|
6143
6267
|
import path42 from "path";
|
|
6144
6268
|
init_errors();
|
|
6145
|
-
init_http_client();
|
|
6146
6269
|
|
|
6147
6270
|
// src/providers/openrouter/payload.ts
|
|
6148
6271
|
var IMAGE_ONLY_MODEL_PREFIXES = [
|
|
@@ -6155,15 +6278,15 @@ function resolveModalities(model) {
|
|
|
6155
6278
|
return IMAGE_ONLY_MODEL_PREFIXES.some((p) => lower.startsWith(p)) ? ["image"] : ["image", "text"];
|
|
6156
6279
|
}
|
|
6157
6280
|
function buildOpenRouterHeaders(apiKey) {
|
|
6158
|
-
const
|
|
6281
|
+
const headers2 = {
|
|
6159
6282
|
Authorization: `Bearer ${apiKey}`,
|
|
6160
6283
|
"Content-Type": "application/json"
|
|
6161
6284
|
};
|
|
6162
6285
|
const referer = resolveKey("OPENROUTER_SITE_URL");
|
|
6163
6286
|
const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
|
|
6164
|
-
if (referer)
|
|
6165
|
-
if (title)
|
|
6166
|
-
return
|
|
6287
|
+
if (referer) headers2["HTTP-Referer"] = referer;
|
|
6288
|
+
if (title) headers2["X-Title"] = title;
|
|
6289
|
+
return headers2;
|
|
6167
6290
|
}
|
|
6168
6291
|
function extractImagesFromResponse(data) {
|
|
6169
6292
|
const out = { urls: [] };
|
|
@@ -6306,7 +6429,7 @@ async function generateOpenRouterImage(opts) {
|
|
|
6306
6429
|
try {
|
|
6307
6430
|
const bytes = await fetchBytes(url);
|
|
6308
6431
|
const dest = path42.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
6309
|
-
|
|
6432
|
+
fs40.writeFileSync(dest, bytes);
|
|
6310
6433
|
savedFiles.push(dest);
|
|
6311
6434
|
logger?.success(`Saved: ${dest}`);
|
|
6312
6435
|
} catch (e) {
|
|
@@ -6317,8 +6440,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
6317
6440
|
}
|
|
6318
6441
|
}
|
|
6319
6442
|
if (output && savedFiles[0]) {
|
|
6320
|
-
|
|
6321
|
-
|
|
6443
|
+
fs40.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
|
|
6444
|
+
fs40.writeFileSync(output, fs40.readFileSync(savedFiles[0]));
|
|
6322
6445
|
}
|
|
6323
6446
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
6324
6447
|
}
|
|
@@ -6359,10 +6482,9 @@ Generated ${finalImages.length} image(s):`);
|
|
|
6359
6482
|
}
|
|
6360
6483
|
|
|
6361
6484
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
6362
|
-
import
|
|
6485
|
+
import fs41 from "fs";
|
|
6363
6486
|
import path43 from "path";
|
|
6364
6487
|
init_errors();
|
|
6365
|
-
init_http_client();
|
|
6366
6488
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
6367
6489
|
var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
|
|
6368
6490
|
var HARD_LIMIT_BYTES2 = 8 * 1024 * 1024;
|
|
@@ -6427,7 +6549,7 @@ function parseStrength(raw) {
|
|
|
6427
6549
|
}
|
|
6428
6550
|
async function runImageToImage(opts) {
|
|
6429
6551
|
const apiKey = requireOpenRouterKey();
|
|
6430
|
-
const
|
|
6552
|
+
const headers2 = buildOpenRouterHeaders(apiKey);
|
|
6431
6553
|
const payload = buildI2IPayload({
|
|
6432
6554
|
prompt: opts.prompt,
|
|
6433
6555
|
model: opts.model,
|
|
@@ -6438,7 +6560,7 @@ async function runImageToImage(opts) {
|
|
|
6438
6560
|
const data = await httpJson({
|
|
6439
6561
|
url: OPENROUTER_API_URL,
|
|
6440
6562
|
method: "POST",
|
|
6441
|
-
headers,
|
|
6563
|
+
headers: headers2,
|
|
6442
6564
|
body: payload,
|
|
6443
6565
|
timeoutMs: 24e4
|
|
6444
6566
|
});
|
|
@@ -6454,31 +6576,30 @@ async function runImageToImage(opts) {
|
|
|
6454
6576
|
if (!url) continue;
|
|
6455
6577
|
const bytes = await fetchBytes(url);
|
|
6456
6578
|
const dest = path43.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
6457
|
-
|
|
6579
|
+
fs41.writeFileSync(dest, bytes);
|
|
6458
6580
|
saved.push(dest);
|
|
6459
6581
|
opts.logger.success(`Saved: ${dest}`);
|
|
6460
6582
|
}
|
|
6461
6583
|
if (opts.output && saved[0]) {
|
|
6462
|
-
|
|
6463
|
-
|
|
6584
|
+
fs41.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
|
|
6585
|
+
fs41.copyFileSync(saved[0], opts.output);
|
|
6464
6586
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
6465
6587
|
}
|
|
6466
6588
|
return saved;
|
|
6467
6589
|
}
|
|
6468
6590
|
|
|
6469
6591
|
// src/providers/openrouter/video-client.ts
|
|
6470
|
-
init_http_client();
|
|
6471
6592
|
var OPENROUTER_VIDEOS_URL = "https://openrouter.ai/api/v1/videos";
|
|
6472
6593
|
function authHeaders5(apiKey) {
|
|
6473
|
-
const
|
|
6594
|
+
const headers2 = {
|
|
6474
6595
|
Authorization: `Bearer ${apiKey}`,
|
|
6475
6596
|
"Content-Type": "application/json"
|
|
6476
6597
|
};
|
|
6477
6598
|
const referer = resolveKey("OPENROUTER_SITE_URL");
|
|
6478
6599
|
const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
|
|
6479
|
-
if (referer)
|
|
6480
|
-
if (title)
|
|
6481
|
-
return
|
|
6600
|
+
if (referer) headers2["HTTP-Referer"] = referer;
|
|
6601
|
+
if (title) headers2["X-Title"] = title;
|
|
6602
|
+
return headers2;
|
|
6482
6603
|
}
|
|
6483
6604
|
async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
|
|
6484
6605
|
return httpJson({
|
|
@@ -6506,7 +6627,7 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
6506
6627
|
}
|
|
6507
6628
|
|
|
6508
6629
|
// src/providers/openrouter/commands/video-status.ts
|
|
6509
|
-
import
|
|
6630
|
+
import fs42 from "fs";
|
|
6510
6631
|
import path44 from "path";
|
|
6511
6632
|
async function downloadOpenRouterVideo(opts) {
|
|
6512
6633
|
const { jobId, logger, source } = opts;
|
|
@@ -6522,14 +6643,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
6522
6643
|
logger.error("Empty response body");
|
|
6523
6644
|
process.exit(1);
|
|
6524
6645
|
}
|
|
6525
|
-
|
|
6526
|
-
const ws =
|
|
6646
|
+
fs42.mkdirSync(path44.dirname(dest), { recursive: true });
|
|
6647
|
+
const ws = fs42.createWriteStream(dest);
|
|
6527
6648
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
6528
6649
|
await pipeline2(r.body, ws);
|
|
6529
6650
|
logger.success(`Saved ${dest}`);
|
|
6530
6651
|
if (opts.output) {
|
|
6531
|
-
|
|
6532
|
-
|
|
6652
|
+
fs42.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
|
|
6653
|
+
fs42.copyFileSync(dest, opts.output);
|
|
6533
6654
|
logger.success(`Copied to ${opts.output}`);
|
|
6534
6655
|
}
|
|
6535
6656
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -6715,12 +6836,12 @@ async function checkBinary(binary, versionFlag = "--version") {
|
|
|
6715
6836
|
|
|
6716
6837
|
// src/commands/check-helpers/gemini-ping.ts
|
|
6717
6838
|
init_errors();
|
|
6718
|
-
init_http_client();
|
|
6719
6839
|
var MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
6720
6840
|
async function pingGemini(apiKey) {
|
|
6721
6841
|
try {
|
|
6722
6842
|
const resp = await httpJson({
|
|
6723
|
-
url:
|
|
6843
|
+
url: MODELS_URL,
|
|
6844
|
+
headers: { "x-goog-api-key": apiKey },
|
|
6724
6845
|
timeoutMs: 1e4
|
|
6725
6846
|
});
|
|
6726
6847
|
const modelCount = Array.isArray(resp.models) ? resp.models.length : 0;
|
|
@@ -6947,7 +7068,7 @@ function registerCheckCommand(program2) {
|
|
|
6947
7068
|
|
|
6948
7069
|
// src/commands/doc/convert.ts
|
|
6949
7070
|
init_errors();
|
|
6950
|
-
import
|
|
7071
|
+
import fs43 from "fs";
|
|
6951
7072
|
import path45 from "path";
|
|
6952
7073
|
|
|
6953
7074
|
// src/commands/doc/default-prompt.ts
|
|
@@ -6969,7 +7090,7 @@ async function convertDocuments(opts) {
|
|
|
6969
7090
|
const model = opts.model ?? DOC_MODEL_DEFAULT;
|
|
6970
7091
|
const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
|
|
6971
7092
|
for (const f of inputs) {
|
|
6972
|
-
if (!
|
|
7093
|
+
if (!fs43.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
|
|
6973
7094
|
}
|
|
6974
7095
|
let outputPath;
|
|
6975
7096
|
if (output) {
|
|
@@ -6980,7 +7101,7 @@ async function convertDocuments(opts) {
|
|
|
6980
7101
|
} else {
|
|
6981
7102
|
outputPath = path45.join(getOutputDir(), "document-extraction.md");
|
|
6982
7103
|
}
|
|
6983
|
-
|
|
7104
|
+
fs43.mkdirSync(path45.dirname(outputPath), { recursive: true });
|
|
6984
7105
|
const results = [];
|
|
6985
7106
|
for (let i = 0; i < inputs.length; i++) {
|
|
6986
7107
|
const filePath = inputs[i];
|
|
@@ -7012,7 +7133,7 @@ async function convertDocuments(opts) {
|
|
|
7012
7133
|
if (attempt < 2) {
|
|
7013
7134
|
const wait = 2 ** attempt * 1e3;
|
|
7014
7135
|
logger?.debug(`Retry ${attempt + 1} after ${wait / 1e3}s: ${errorMsg}`);
|
|
7015
|
-
await
|
|
7136
|
+
await sleep4(wait);
|
|
7016
7137
|
}
|
|
7017
7138
|
}
|
|
7018
7139
|
}
|
|
@@ -7044,7 +7165,7 @@ async function convertDocuments(opts) {
|
|
|
7044
7165
|
}
|
|
7045
7166
|
lines.push("\n---\n");
|
|
7046
7167
|
}
|
|
7047
|
-
|
|
7168
|
+
fs43.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
7048
7169
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
7049
7170
|
console.log(`
|
|
7050
7171
|
${"=".repeat(50)}`);
|
|
@@ -7060,7 +7181,7 @@ ${"=".repeat(50)}`);
|
|
|
7060
7181
|
const failed = results.filter((r) => r.status === "error").length;
|
|
7061
7182
|
if (failed > 0) process.exit(1);
|
|
7062
7183
|
}
|
|
7063
|
-
function
|
|
7184
|
+
function sleep4(ms) {
|
|
7064
7185
|
return new Promise((r) => setTimeout(r, ms));
|
|
7065
7186
|
}
|
|
7066
7187
|
|
|
@@ -7092,11 +7213,11 @@ function registerDocCommands(program2) {
|
|
|
7092
7213
|
|
|
7093
7214
|
// src/commands/media/index.ts
|
|
7094
7215
|
init_errors();
|
|
7095
|
-
import
|
|
7216
|
+
import fs46 from "fs";
|
|
7096
7217
|
import path48 from "path";
|
|
7097
7218
|
|
|
7098
7219
|
// src/commands/media/batch.ts
|
|
7099
|
-
import
|
|
7220
|
+
import fs44 from "fs";
|
|
7100
7221
|
import path46 from "path";
|
|
7101
7222
|
|
|
7102
7223
|
// src/commands/media/detect-type.ts
|
|
@@ -7293,8 +7414,8 @@ async function batchOptimize(opts) {
|
|
|
7293
7414
|
verbose,
|
|
7294
7415
|
logger
|
|
7295
7416
|
} = opts;
|
|
7296
|
-
|
|
7297
|
-
const entries =
|
|
7417
|
+
fs44.mkdirSync(outputDir, { recursive: true });
|
|
7418
|
+
const entries = fs44.readdirSync(inputDir);
|
|
7298
7419
|
const mediaFiles = entries.filter((f) => {
|
|
7299
7420
|
const ext = path46.extname(f).toLowerCase();
|
|
7300
7421
|
return ALL_EXTS.has(ext);
|
|
@@ -7346,7 +7467,7 @@ async function batchOptimize(opts) {
|
|
|
7346
7467
|
|
|
7347
7468
|
// src/commands/media/split-video.ts
|
|
7348
7469
|
init_errors();
|
|
7349
|
-
import
|
|
7470
|
+
import fs45 from "fs";
|
|
7350
7471
|
import path47 from "path";
|
|
7351
7472
|
async function splitVideo(opts) {
|
|
7352
7473
|
await assertFfmpeg();
|
|
@@ -7358,7 +7479,7 @@ async function splitVideo(opts) {
|
|
|
7358
7479
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
7359
7480
|
return [input];
|
|
7360
7481
|
}
|
|
7361
|
-
|
|
7482
|
+
fs45.mkdirSync(outputDir, { recursive: true });
|
|
7362
7483
|
const ext = path47.extname(input) || ".mp4";
|
|
7363
7484
|
const baseName = path47.basename(input, ext);
|
|
7364
7485
|
const outputPattern = path47.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
@@ -7381,7 +7502,7 @@ async function splitVideo(opts) {
|
|
|
7381
7502
|
const files = [];
|
|
7382
7503
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
7383
7504
|
const fname = path47.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
7384
|
-
if (
|
|
7505
|
+
if (fs45.existsSync(fname)) files.push(fname);
|
|
7385
7506
|
}
|
|
7386
7507
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
7387
7508
|
return files;
|
|
@@ -7395,8 +7516,8 @@ function registerMediaCommands(program2) {
|
|
|
7395
7516
|
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(
|
|
7396
7517
|
async (opts) => {
|
|
7397
7518
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7398
|
-
if (!
|
|
7399
|
-
|
|
7519
|
+
if (!fs46.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
7520
|
+
fs46.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
|
|
7400
7521
|
const ext = path48.extname(opts.input).toLowerCase();
|
|
7401
7522
|
const kind = extToKind(ext);
|
|
7402
7523
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
@@ -7437,7 +7558,7 @@ function registerMediaCommands(program2) {
|
|
|
7437
7558
|
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(
|
|
7438
7559
|
async (opts) => {
|
|
7439
7560
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7440
|
-
if (!
|
|
7561
|
+
if (!fs46.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
7441
7562
|
const chunks = await splitVideo({
|
|
7442
7563
|
input: opts.input,
|
|
7443
7564
|
outputDir: opts.outputDir,
|
|
@@ -7453,7 +7574,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
7453
7574
|
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(
|
|
7454
7575
|
async (opts) => {
|
|
7455
7576
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7456
|
-
if (!
|
|
7577
|
+
if (!fs46.existsSync(opts.inputDir))
|
|
7457
7578
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
7458
7579
|
const result = await batchOptimize({
|
|
7459
7580
|
inputDir: opts.inputDir,
|
|
@@ -7476,7 +7597,6 @@ Processed: ${result.succeeded}/${result.total} files`);
|
|
|
7476
7597
|
|
|
7477
7598
|
// src/commands/update.ts
|
|
7478
7599
|
init_errors();
|
|
7479
|
-
init_http_client();
|
|
7480
7600
|
import { createRequire } from "module";
|
|
7481
7601
|
import { execa as execa5 } from "execa";
|
|
7482
7602
|
|
|
@@ -7640,7 +7760,7 @@ function registerCommands(program2) {
|
|
|
7640
7760
|
|
|
7641
7761
|
// src/core/index.ts
|
|
7642
7762
|
init_errors();
|
|
7643
|
-
|
|
7763
|
+
init_redact();
|
|
7644
7764
|
|
|
7645
7765
|
// src/cli.ts
|
|
7646
7766
|
loadEnv();
|