@mrgoonie/multix 0.7.2 → 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 +7 -0
- package/dist/cli.js +432 -410
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -95,151 +95,6 @@ var init_errors = __esm({
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
// src/core/fetch-json.ts
|
|
99
|
-
function parseRetryAfter(value, now = Date.now()) {
|
|
100
|
-
if (!value) return void 0;
|
|
101
|
-
const trimmed = value.trim();
|
|
102
|
-
if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1e3);
|
|
103
|
-
const date = Date.parse(trimmed);
|
|
104
|
-
if (Number.isNaN(date)) return void 0;
|
|
105
|
-
return Math.max(0, date - now);
|
|
106
|
-
}
|
|
107
|
-
async function httpErrorFromResponse(response, url) {
|
|
108
|
-
let snippet = "";
|
|
109
|
-
try {
|
|
110
|
-
const text = await response.text();
|
|
111
|
-
snippet = text.slice(0, SNIPPET_MAX);
|
|
112
|
-
} catch {
|
|
113
|
-
snippet = "(could not read body)";
|
|
114
|
-
}
|
|
115
|
-
return new HttpError(
|
|
116
|
-
response.status,
|
|
117
|
-
snippet,
|
|
118
|
-
url,
|
|
119
|
-
parseRetryAfter(response.headers.get("retry-after"))
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
async function fetchJson(opts) {
|
|
123
|
-
const { url, method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
124
|
-
const doFetch = opts.fetch ?? ((input, init2) => globalThis.fetch(input, init2));
|
|
125
|
-
const init = {
|
|
126
|
-
method,
|
|
127
|
-
headers: {
|
|
128
|
-
"Content-Type": "application/json",
|
|
129
|
-
...headers
|
|
130
|
-
},
|
|
131
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
132
|
-
};
|
|
133
|
-
if (body !== void 0) {
|
|
134
|
-
init.body = JSON.stringify(body);
|
|
135
|
-
}
|
|
136
|
-
let response;
|
|
137
|
-
try {
|
|
138
|
-
response = await doFetch(url, init);
|
|
139
|
-
} catch (cause) {
|
|
140
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
141
|
-
throw new HttpError(0, msg, url);
|
|
142
|
-
}
|
|
143
|
-
if (!response.ok) {
|
|
144
|
-
throw await httpErrorFromResponse(response, url);
|
|
145
|
-
}
|
|
146
|
-
let text;
|
|
147
|
-
try {
|
|
148
|
-
text = await response.text();
|
|
149
|
-
} catch (cause) {
|
|
150
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
151
|
-
throw new HttpError(0, `Failed to read response body: ${msg}`, url);
|
|
152
|
-
}
|
|
153
|
-
if (text.trim() === "") return void 0;
|
|
154
|
-
try {
|
|
155
|
-
return JSON.parse(text);
|
|
156
|
-
} catch {
|
|
157
|
-
throw new HttpError(
|
|
158
|
-
response.status,
|
|
159
|
-
`Invalid JSON response: ${text.slice(0, SNIPPET_MAX)}`,
|
|
160
|
-
url
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
var DEFAULT_TIMEOUT_MS, SNIPPET_MAX;
|
|
165
|
-
var init_fetch_json = __esm({
|
|
166
|
-
"src/core/fetch-json.ts"() {
|
|
167
|
-
"use strict";
|
|
168
|
-
init_errors();
|
|
169
|
-
DEFAULT_TIMEOUT_MS = 12e4;
|
|
170
|
-
SNIPPET_MAX = 500;
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// src/core/http-client.ts
|
|
175
|
-
var http_client_exports = {};
|
|
176
|
-
__export(http_client_exports, {
|
|
177
|
-
downloadFile: () => downloadFile,
|
|
178
|
-
fetchBytes: () => fetchBytes,
|
|
179
|
-
httpJson: () => httpJson
|
|
180
|
-
});
|
|
181
|
-
import { once } from "events";
|
|
182
|
-
import fs4 from "fs";
|
|
183
|
-
import path4 from "path";
|
|
184
|
-
import { pipeline } from "stream/promises";
|
|
185
|
-
async function httpJson(opts) {
|
|
186
|
-
return fetchJson(opts);
|
|
187
|
-
}
|
|
188
|
-
async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS, headers) {
|
|
189
|
-
fs4.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
190
|
-
let response;
|
|
191
|
-
try {
|
|
192
|
-
response = await globalThis.fetch(url, {
|
|
193
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
194
|
-
...headers ? { headers } : {}
|
|
195
|
-
});
|
|
196
|
-
} catch (cause) {
|
|
197
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
198
|
-
throw new HttpError(0, msg, url);
|
|
199
|
-
}
|
|
200
|
-
if (!response.ok) {
|
|
201
|
-
throw new HttpError(response.status, "(download failed)", url);
|
|
202
|
-
}
|
|
203
|
-
if (!response.body) {
|
|
204
|
-
throw new HttpError(0, "Response body is null", url);
|
|
205
|
-
}
|
|
206
|
-
const fileStream = fs4.createWriteStream(dest);
|
|
207
|
-
try {
|
|
208
|
-
await pipeline(response.body, fileStream);
|
|
209
|
-
} catch (err2) {
|
|
210
|
-
if (!fileStream.closed) await once(fileStream, "close");
|
|
211
|
-
throw err2;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
async function fetchBytes(urlOrDataUri) {
|
|
215
|
-
if (urlOrDataUri.startsWith("data:")) {
|
|
216
|
-
const commaIdx = urlOrDataUri.indexOf(",");
|
|
217
|
-
if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
|
|
218
|
-
const encoded = urlOrDataUri.slice(commaIdx + 1);
|
|
219
|
-
return Buffer.from(encoded, "base64");
|
|
220
|
-
}
|
|
221
|
-
let response;
|
|
222
|
-
try {
|
|
223
|
-
response = await globalThis.fetch(urlOrDataUri, {
|
|
224
|
-
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
225
|
-
});
|
|
226
|
-
} catch (cause) {
|
|
227
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
228
|
-
throw new HttpError(0, msg, urlOrDataUri);
|
|
229
|
-
}
|
|
230
|
-
if (!response.ok) {
|
|
231
|
-
throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
|
|
232
|
-
}
|
|
233
|
-
return Buffer.from(await response.arrayBuffer());
|
|
234
|
-
}
|
|
235
|
-
var init_http_client = __esm({
|
|
236
|
-
"src/core/http-client.ts"() {
|
|
237
|
-
"use strict";
|
|
238
|
-
init_errors();
|
|
239
|
-
init_fetch_json();
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
98
|
// src/cli.ts
|
|
244
99
|
import { createRequire as createRequire2 } from "module";
|
|
245
100
|
import { Command } from "commander";
|
|
@@ -447,7 +302,135 @@ function resolveKey(name) {
|
|
|
447
302
|
|
|
448
303
|
// src/providers/byteplus/client.ts
|
|
449
304
|
init_errors();
|
|
450
|
-
|
|
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
|
+
}
|
|
451
434
|
|
|
452
435
|
// src/providers/byteplus/models.ts
|
|
453
436
|
var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
|
|
@@ -527,7 +510,6 @@ function createBytePlusClient() {
|
|
|
527
510
|
}
|
|
528
511
|
|
|
529
512
|
// src/providers/byteplus/generators/three-d.ts
|
|
530
|
-
init_http_client();
|
|
531
513
|
function buildThreeDTaskBody(opts) {
|
|
532
514
|
const promptText = (opts.prompt ?? "").trim();
|
|
533
515
|
const flagPart = opts.flags?.trim() ?? "";
|
|
@@ -835,7 +817,6 @@ function finalizeGeneratedImages(files, opts) {
|
|
|
835
817
|
}
|
|
836
818
|
|
|
837
819
|
// src/providers/byteplus/generators/image.ts
|
|
838
|
-
init_http_client();
|
|
839
820
|
import fs7 from "fs";
|
|
840
821
|
import path7 from "path";
|
|
841
822
|
var ASPECT_TO_SIZE = {
|
|
@@ -999,7 +980,6 @@ function collect2(value, prev) {
|
|
|
999
980
|
import path9 from "path";
|
|
1000
981
|
|
|
1001
982
|
// src/core/video-thumb.ts
|
|
1002
|
-
init_http_client();
|
|
1003
983
|
import fs8 from "fs";
|
|
1004
984
|
import path8 from "path";
|
|
1005
985
|
var THUMB_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -1084,7 +1064,6 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
|
|
|
1084
1064
|
}
|
|
1085
1065
|
|
|
1086
1066
|
// src/providers/byteplus/generators/video.ts
|
|
1087
|
-
init_http_client();
|
|
1088
1067
|
function buildFlagSuffix(opts) {
|
|
1089
1068
|
const parts = [];
|
|
1090
1069
|
if (opts.resolution) parts.push(`--rs ${opts.resolution}`);
|
|
@@ -1895,7 +1874,6 @@ function registerCloudflareCommands(program2) {
|
|
|
1895
1874
|
import fs10 from "fs";
|
|
1896
1875
|
import path14 from "path";
|
|
1897
1876
|
init_errors();
|
|
1898
|
-
init_http_client();
|
|
1899
1877
|
|
|
1900
1878
|
// src/providers/elevenlabs/models.ts
|
|
1901
1879
|
var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
|
|
@@ -2004,19 +1982,19 @@ async function apiPostBinary(endpoint2, body, apiKey, opts = {}) {
|
|
|
2004
1982
|
if (opts.query) {
|
|
2005
1983
|
for (const [k, v] of Object.entries(opts.query)) url.searchParams.set(k, v);
|
|
2006
1984
|
}
|
|
2007
|
-
const
|
|
1985
|
+
const headers2 = authHeaders(apiKey);
|
|
2008
1986
|
let init;
|
|
2009
1987
|
if (body instanceof FormData) {
|
|
2010
1988
|
init = {
|
|
2011
1989
|
method: "POST",
|
|
2012
|
-
headers,
|
|
1990
|
+
headers: headers2,
|
|
2013
1991
|
body,
|
|
2014
1992
|
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
2015
1993
|
};
|
|
2016
1994
|
} else {
|
|
2017
1995
|
init = {
|
|
2018
1996
|
method: "POST",
|
|
2019
|
-
headers: { ...
|
|
1997
|
+
headers: { ...headers2, "Content-Type": "application/json" },
|
|
2020
1998
|
body: JSON.stringify(body),
|
|
2021
1999
|
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
2022
2000
|
};
|
|
@@ -2185,7 +2163,6 @@ function registerCloneCommand(parent) {
|
|
|
2185
2163
|
|
|
2186
2164
|
// src/providers/elevenlabs/commands/dub.ts
|
|
2187
2165
|
init_errors();
|
|
2188
|
-
init_http_client();
|
|
2189
2166
|
import fs12 from "fs";
|
|
2190
2167
|
import path16 from "path";
|
|
2191
2168
|
function registerDubCommand(parent) {
|
|
@@ -2669,7 +2646,6 @@ var FAL_DEFAULTS = {
|
|
|
2669
2646
|
};
|
|
2670
2647
|
|
|
2671
2648
|
// src/providers/fal/run-helpers.ts
|
|
2672
|
-
init_http_client();
|
|
2673
2649
|
import fs16 from "fs";
|
|
2674
2650
|
import path19 from "path";
|
|
2675
2651
|
|
|
@@ -2706,7 +2682,6 @@ init_redact();
|
|
|
2706
2682
|
// src/providers/fal/client.ts
|
|
2707
2683
|
init_errors();
|
|
2708
2684
|
init_errors();
|
|
2709
|
-
init_http_client();
|
|
2710
2685
|
init_redact();
|
|
2711
2686
|
function requireFalKey() {
|
|
2712
2687
|
const key = resolveKey("FAL_KEY");
|
|
@@ -3118,7 +3093,6 @@ import fs20 from "fs";
|
|
|
3118
3093
|
import fs18 from "fs";
|
|
3119
3094
|
import path22 from "path";
|
|
3120
3095
|
init_errors();
|
|
3121
|
-
init_http_client();
|
|
3122
3096
|
|
|
3123
3097
|
// src/providers/gemini/task-resolver.ts
|
|
3124
3098
|
import path21 from "path";
|
|
@@ -3813,11 +3787,179 @@ function collectSpeakers(value, prev) {
|
|
|
3813
3787
|
}
|
|
3814
3788
|
|
|
3815
3789
|
// src/providers/gemini/commands/generate-video.ts
|
|
3816
|
-
import
|
|
3790
|
+
import fs24 from "fs";
|
|
3817
3791
|
import path25 from "path";
|
|
3818
3792
|
init_errors();
|
|
3819
|
-
|
|
3820
|
-
|
|
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
|
|
3821
3963
|
function registerGenerateVideoCommand(parent) {
|
|
3822
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(
|
|
3823
3965
|
"--reference-images <paths...>",
|
|
@@ -3841,72 +3983,24 @@ function registerGenerateVideoCommand(parent) {
|
|
|
3841
3983
|
}
|
|
3842
3984
|
logger.debug(`Submitting video generation with ${model}...`);
|
|
3843
3985
|
logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
|
|
3844
|
-
const
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
}
|
|
3859
|
-
const headers = { "x-goog-api-key": apiKey, "Content-Type": "application/json" };
|
|
3860
|
-
let operation;
|
|
3861
|
-
try {
|
|
3862
|
-
operation = await httpJson({
|
|
3863
|
-
url: `${BASE2}/v1beta/models/${model}:generateVideos`,
|
|
3864
|
-
method: "POST",
|
|
3865
|
-
headers,
|
|
3866
|
-
body: {
|
|
3867
|
-
contents: [{ parts: contentParts }],
|
|
3868
|
-
generationConfig: config
|
|
3869
|
-
}
|
|
3870
|
-
});
|
|
3871
|
-
} catch (e) {
|
|
3872
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
3873
|
-
throw new ProviderError(`Video generation submit failed: ${msg}`, "gemini");
|
|
3874
|
-
}
|
|
3875
|
-
const operationName = operation.name;
|
|
3876
|
-
logger.debug(`Operation: ${operationName}`);
|
|
3877
|
-
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
3878
|
-
let done = false;
|
|
3879
|
-
let finalOp = operation;
|
|
3880
|
-
while (!done && Date.now() < deadline) {
|
|
3881
|
-
await sleep3(1e4);
|
|
3882
|
-
finalOp = await httpJson({
|
|
3883
|
-
url: `${BASE2}/v1beta/${operationName}`,
|
|
3884
|
-
headers
|
|
3885
|
-
});
|
|
3886
|
-
done = finalOp.done === true;
|
|
3887
|
-
if (!done)
|
|
3888
|
-
logger.debug(
|
|
3889
|
-
`Still generating... (${Math.round((deadline - Date.now()) / 1e3)}s left)`
|
|
3890
|
-
);
|
|
3891
|
-
}
|
|
3892
|
-
if (!done) throw new ProviderError("Video generation timed out after 10 minutes", "gemini");
|
|
3893
|
-
if (finalOp.error)
|
|
3894
|
-
throw new ProviderError(`Veo error: ${JSON.stringify(finalOp.error)}`, "gemini");
|
|
3895
|
-
const videoUri = finalOp.response?.generatedSamples?.[0]?.video?.uri ?? finalOp.response?.generatedVideos?.[0]?.video?.uri;
|
|
3896
|
-
if (!videoUri) {
|
|
3897
|
-
throw new ProviderError(
|
|
3898
|
-
`Unexpected Veo response structure: ${JSON.stringify(finalOp.response)}`,
|
|
3899
|
-
"gemini"
|
|
3900
|
-
);
|
|
3901
|
-
}
|
|
3902
|
-
const outDir = getOutputDir();
|
|
3903
|
-
const dest = path25.join(outDir, `veo_generated_${Date.now()}.mp4`);
|
|
3904
|
-
const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
|
|
3905
|
-
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
|
+
);
|
|
3906
4000
|
logger.success(`Saved video: ${dest}`);
|
|
3907
4001
|
if (opts.output) {
|
|
3908
|
-
|
|
3909
|
-
|
|
4002
|
+
fs24.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
|
|
4003
|
+
fs24.copyFileSync(dest, opts.output);
|
|
3910
4004
|
logger.success(`Copied to: ${opts.output}`);
|
|
3911
4005
|
}
|
|
3912
4006
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -3919,13 +4013,10 @@ Generated video: ${dest}`);
|
|
|
3919
4013
|
}
|
|
3920
4014
|
);
|
|
3921
4015
|
}
|
|
3922
|
-
function sleep3(ms) {
|
|
3923
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
3924
|
-
}
|
|
3925
4016
|
|
|
3926
4017
|
// src/providers/gemini/commands/generate.ts
|
|
3927
4018
|
init_errors();
|
|
3928
|
-
import
|
|
4019
|
+
import fs25 from "fs";
|
|
3929
4020
|
import path26 from "path";
|
|
3930
4021
|
function registerGenerateCommand(parent) {
|
|
3931
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(
|
|
@@ -3969,7 +4060,7 @@ function registerGenerateCommand(parent) {
|
|
|
3969
4060
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
3970
4061
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
3971
4062
|
const dest = path26.join(outDir, fname);
|
|
3972
|
-
|
|
4063
|
+
fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
3973
4064
|
savedFiles.push(dest);
|
|
3974
4065
|
logger.success(`Saved: ${dest}`);
|
|
3975
4066
|
}
|
|
@@ -3984,8 +4075,8 @@ function registerGenerateCommand(parent) {
|
|
|
3984
4075
|
process.exit(1);
|
|
3985
4076
|
}
|
|
3986
4077
|
if (opts.output && savedFiles[0]) {
|
|
3987
|
-
|
|
3988
|
-
|
|
4078
|
+
fs25.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
|
|
4079
|
+
fs25.copyFileSync(savedFiles[0], opts.output);
|
|
3989
4080
|
logger.success(`Copied to: ${opts.output}`);
|
|
3990
4081
|
}
|
|
3991
4082
|
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
@@ -3997,7 +4088,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
3997
4088
|
}
|
|
3998
4089
|
|
|
3999
4090
|
// src/providers/gemini/commands/image-to-image.ts
|
|
4000
|
-
import
|
|
4091
|
+
import fs26 from "fs";
|
|
4001
4092
|
import path27 from "path";
|
|
4002
4093
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
4003
4094
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
@@ -4060,13 +4151,13 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
4060
4151
|
if (!img) continue;
|
|
4061
4152
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
4062
4153
|
const dest = path27.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
4063
|
-
|
|
4154
|
+
fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
4064
4155
|
saved.push(dest);
|
|
4065
4156
|
logger.success(`Saved: ${dest}`);
|
|
4066
4157
|
}
|
|
4067
4158
|
if (opts.output && saved[0]) {
|
|
4068
|
-
|
|
4069
|
-
|
|
4159
|
+
fs26.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
|
|
4160
|
+
fs26.copyFileSync(saved[0], opts.output);
|
|
4070
4161
|
logger.success(`Copied to: ${opts.output}`);
|
|
4071
4162
|
}
|
|
4072
4163
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4102,23 +4193,9 @@ function sniffImageMime(buf, url) {
|
|
|
4102
4193
|
}
|
|
4103
4194
|
|
|
4104
4195
|
// src/providers/gemini/commands/image-to-video.ts
|
|
4105
|
-
import
|
|
4196
|
+
import fs27 from "fs";
|
|
4106
4197
|
import path28 from "path";
|
|
4107
4198
|
init_errors();
|
|
4108
|
-
init_http_client();
|
|
4109
|
-
var BASE3 = "https://generativelanguage.googleapis.com";
|
|
4110
|
-
function imagePart(imgPath) {
|
|
4111
|
-
if (!fs26.existsSync(imgPath)) {
|
|
4112
|
-
throw new ValidationError(`Image not found: ${imgPath}`);
|
|
4113
|
-
}
|
|
4114
|
-
const data = fs26.readFileSync(imgPath).toString("base64");
|
|
4115
|
-
const ext = path28.extname(imgPath).toLowerCase();
|
|
4116
|
-
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
4117
|
-
return { inlineData: { mimeType, data } };
|
|
4118
|
-
}
|
|
4119
|
-
function sleep4(ms) {
|
|
4120
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
4121
|
-
}
|
|
4122
4199
|
function registerGeminiImageToVideoCommand(parent) {
|
|
4123
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(
|
|
4124
4201
|
async (imagePath, opts) => {
|
|
@@ -4137,60 +4214,27 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
4137
4214
|
if (!ASPECT_RATIOS.includes(opts.aspectRatio)) {
|
|
4138
4215
|
throw new ValidationError(`Invalid aspect ratio: ${opts.aspectRatio}`);
|
|
4139
4216
|
}
|
|
4140
|
-
const contentParts = [
|
|
4141
|
-
{ text: opts.prompt },
|
|
4142
|
-
imagePart(imagePath)
|
|
4143
|
-
];
|
|
4144
|
-
if (opts.lastFrame) contentParts.push(imagePart(opts.lastFrame));
|
|
4145
|
-
const headers = { "x-goog-api-key": apiKey, "Content-Type": "application/json" };
|
|
4146
4217
|
logger.info(
|
|
4147
4218
|
"Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
|
|
4148
4219
|
);
|
|
4149
|
-
let operation;
|
|
4150
|
-
try {
|
|
4151
|
-
operation = await httpJson({
|
|
4152
|
-
url: `${BASE3}/v1beta/models/${model}:generateVideos`,
|
|
4153
|
-
method: "POST",
|
|
4154
|
-
headers,
|
|
4155
|
-
body: {
|
|
4156
|
-
contents: [{ parts: contentParts }],
|
|
4157
|
-
generationConfig: { aspectRatio: opts.aspectRatio, resolution: opts.resolution }
|
|
4158
|
-
}
|
|
4159
|
-
});
|
|
4160
|
-
} catch (e) {
|
|
4161
|
-
throw new ProviderError(
|
|
4162
|
-
`Submit failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
4163
|
-
"gemini"
|
|
4164
|
-
);
|
|
4165
|
-
}
|
|
4166
|
-
const operationName = operation.name;
|
|
4167
|
-
logger.debug(`Operation: ${operationName}`);
|
|
4168
|
-
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
4169
|
-
let finalOp = operation;
|
|
4170
|
-
while (!finalOp.done && Date.now() < deadline) {
|
|
4171
|
-
await sleep4(1e4);
|
|
4172
|
-
finalOp = await httpJson({ url: `${BASE3}/v1beta/${operationName}`, headers });
|
|
4173
|
-
if (!finalOp.done) {
|
|
4174
|
-
logger.debug(`Still generating... ${Math.round((deadline - Date.now()) / 1e3)}s left`);
|
|
4175
|
-
}
|
|
4176
|
-
}
|
|
4177
|
-
if (!finalOp.done) throw new ProviderError("Veo timed out after 10 minutes", "gemini");
|
|
4178
|
-
if (finalOp.error) {
|
|
4179
|
-
throw new ProviderError(`Veo error: ${JSON.stringify(finalOp.error)}`, "gemini");
|
|
4180
|
-
}
|
|
4181
|
-
const videoUri = finalOp.response?.generatedSamples?.[0]?.video?.uri ?? finalOp.response?.generatedVideos?.[0]?.video?.uri;
|
|
4182
|
-
if (!videoUri) {
|
|
4183
|
-
throw new ProviderError(
|
|
4184
|
-
`Unexpected Veo response: ${JSON.stringify(finalOp.response)}`,
|
|
4185
|
-
"gemini"
|
|
4186
|
-
);
|
|
4187
|
-
}
|
|
4188
4220
|
const dest = path28.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
4189
|
-
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
|
+
);
|
|
4190
4234
|
logger.success(`Saved video: ${dest}`);
|
|
4191
4235
|
if (opts.output) {
|
|
4192
|
-
|
|
4193
|
-
|
|
4236
|
+
fs27.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
|
|
4237
|
+
fs27.copyFileSync(dest, opts.output);
|
|
4194
4238
|
logger.success(`Copied to: ${opts.output}`);
|
|
4195
4239
|
}
|
|
4196
4240
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -4206,14 +4250,14 @@ Generated video: ${dest}`);
|
|
|
4206
4250
|
|
|
4207
4251
|
// src/providers/gemini/commands/transcribe.ts
|
|
4208
4252
|
init_errors();
|
|
4209
|
-
import
|
|
4253
|
+
import fs28 from "fs";
|
|
4210
4254
|
function registerTranscribeCommand2(parent) {
|
|
4211
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(
|
|
4212
4256
|
async (opts) => {
|
|
4213
4257
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4214
4258
|
const model = opts.model ?? getDefaultModel("transcribe");
|
|
4215
4259
|
for (const f of opts.files) {
|
|
4216
|
-
if (!
|
|
4260
|
+
if (!fs28.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
4217
4261
|
}
|
|
4218
4262
|
const results = [];
|
|
4219
4263
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -4270,13 +4314,11 @@ function registerGeminiCommands(program2) {
|
|
|
4270
4314
|
}
|
|
4271
4315
|
|
|
4272
4316
|
// src/providers/leonardo/commands/generate.ts
|
|
4273
|
-
|
|
4274
|
-
import fs28 from "fs";
|
|
4317
|
+
import fs29 from "fs";
|
|
4275
4318
|
import path29 from "path";
|
|
4276
4319
|
|
|
4277
4320
|
// src/providers/leonardo/client.ts
|
|
4278
4321
|
init_errors();
|
|
4279
|
-
init_http_client();
|
|
4280
4322
|
|
|
4281
4323
|
// src/providers/leonardo/models.ts
|
|
4282
4324
|
var DEFAULT_LEONARDO_BASE_URL = "https://cloud.leonardo.ai/api/rest/v1";
|
|
@@ -4483,8 +4525,8 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
4483
4525
|
}
|
|
4484
4526
|
if (opts.output && saved[0]) {
|
|
4485
4527
|
const dest = path29.resolve(opts.output);
|
|
4486
|
-
|
|
4487
|
-
|
|
4528
|
+
fs29.mkdirSync(path29.dirname(dest), { recursive: true });
|
|
4529
|
+
fs29.copyFileSync(saved[0], dest);
|
|
4488
4530
|
logger.success(`Copied to ${dest}`);
|
|
4489
4531
|
}
|
|
4490
4532
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4496,8 +4538,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4496
4538
|
}
|
|
4497
4539
|
|
|
4498
4540
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
4499
|
-
|
|
4500
|
-
import fs29 from "fs";
|
|
4541
|
+
import fs30 from "fs";
|
|
4501
4542
|
import path30 from "path";
|
|
4502
4543
|
function extFromUrl3(url, fallback = ".png") {
|
|
4503
4544
|
try {
|
|
@@ -4574,8 +4615,8 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
4574
4615
|
}
|
|
4575
4616
|
if (opts.output && saved[0]) {
|
|
4576
4617
|
const dest = path30.resolve(opts.output);
|
|
4577
|
-
|
|
4578
|
-
|
|
4618
|
+
fs30.mkdirSync(path30.dirname(dest), { recursive: true });
|
|
4619
|
+
fs30.copyFileSync(saved[0], dest);
|
|
4579
4620
|
logger.success(`Copied to ${dest}`);
|
|
4580
4621
|
}
|
|
4581
4622
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4587,7 +4628,6 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4587
4628
|
}
|
|
4588
4629
|
|
|
4589
4630
|
// src/providers/leonardo/commands/video.ts
|
|
4590
|
-
init_http_client();
|
|
4591
4631
|
import path31 from "path";
|
|
4592
4632
|
function v2DimsFor(resolution) {
|
|
4593
4633
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
@@ -4805,7 +4845,6 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
4805
4845
|
}
|
|
4806
4846
|
|
|
4807
4847
|
// src/providers/leonardo/commands/status.ts
|
|
4808
|
-
init_http_client();
|
|
4809
4848
|
import path32 from "path";
|
|
4810
4849
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
4811
4850
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
@@ -4963,7 +5002,6 @@ init_errors();
|
|
|
4963
5002
|
|
|
4964
5003
|
// src/providers/minimax/client.ts
|
|
4965
5004
|
init_errors();
|
|
4966
|
-
init_http_client();
|
|
4967
5005
|
var MINIMAX_BASE_URL = "https://api.minimax.io/v1";
|
|
4968
5006
|
function requireMinimaxKey() {
|
|
4969
5007
|
const key = resolveKey("MINIMAX_API_KEY");
|
|
@@ -5018,7 +5056,7 @@ async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
|
|
|
5018
5056
|
if (status === "Failed" || status === "Error") {
|
|
5019
5057
|
throw new ProviderError(`Task ${taskId} failed: ${JSON.stringify(result)}`, "minimax");
|
|
5020
5058
|
}
|
|
5021
|
-
await
|
|
5059
|
+
await sleep3(intervalMs);
|
|
5022
5060
|
elapsed += intervalMs;
|
|
5023
5061
|
}
|
|
5024
5062
|
throw new ProviderError(`Task ${taskId} timed out after ${timeoutMs / 1e3}s`, "minimax");
|
|
@@ -5037,13 +5075,12 @@ async function downloadMinimaxFile(fileId, apiKey, dest, logger) {
|
|
|
5037
5075
|
await downloadFile(downloadUrl, dest);
|
|
5038
5076
|
return dest;
|
|
5039
5077
|
}
|
|
5040
|
-
function
|
|
5078
|
+
function sleep3(ms) {
|
|
5041
5079
|
return new Promise((r) => setTimeout(r, ms));
|
|
5042
5080
|
}
|
|
5043
5081
|
|
|
5044
5082
|
// src/providers/minimax/generators/music.ts
|
|
5045
|
-
|
|
5046
|
-
import fs30 from "fs";
|
|
5083
|
+
import fs31 from "fs";
|
|
5047
5084
|
import path33 from "path";
|
|
5048
5085
|
async function generateMinimaxMusic(opts) {
|
|
5049
5086
|
const {
|
|
@@ -5093,10 +5130,10 @@ async function generateMinimaxMusic(opts) {
|
|
|
5093
5130
|
try {
|
|
5094
5131
|
if (audioData.startsWith("http")) {
|
|
5095
5132
|
const bytes = await fetchBytes(audioData);
|
|
5096
|
-
|
|
5133
|
+
fs31.writeFileSync(dest, bytes);
|
|
5097
5134
|
} else {
|
|
5098
5135
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
5099
|
-
|
|
5136
|
+
fs31.writeFileSync(dest, audioBytes);
|
|
5100
5137
|
}
|
|
5101
5138
|
} catch (e) {
|
|
5102
5139
|
return {
|
|
@@ -5107,8 +5144,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
5107
5144
|
const durSec = durationMs / 1e3;
|
|
5108
5145
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
5109
5146
|
if (output) {
|
|
5110
|
-
|
|
5111
|
-
|
|
5147
|
+
fs31.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
|
|
5148
|
+
fs31.copyFileSync(dest, output);
|
|
5112
5149
|
}
|
|
5113
5150
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
5114
5151
|
}
|
|
@@ -5182,7 +5219,7 @@ Generated music: ${result.generatedAudio}`);
|
|
|
5182
5219
|
init_errors();
|
|
5183
5220
|
|
|
5184
5221
|
// src/providers/minimax/generators/speech.ts
|
|
5185
|
-
import
|
|
5222
|
+
import fs32 from "fs";
|
|
5186
5223
|
import path34 from "path";
|
|
5187
5224
|
async function generateMinimaxSpeech(opts) {
|
|
5188
5225
|
const {
|
|
@@ -5231,11 +5268,11 @@ async function generateMinimaxSpeech(opts) {
|
|
|
5231
5268
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
5232
5269
|
const outDir = getOutputDir();
|
|
5233
5270
|
const dest = path34.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
5234
|
-
|
|
5271
|
+
fs32.writeFileSync(dest, audioBytes);
|
|
5235
5272
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
5236
5273
|
if (output) {
|
|
5237
|
-
|
|
5238
|
-
|
|
5274
|
+
fs32.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
|
|
5275
|
+
fs32.copyFileSync(dest, output);
|
|
5239
5276
|
}
|
|
5240
5277
|
return { status: "success", generatedAudio: dest, model };
|
|
5241
5278
|
}
|
|
@@ -5282,7 +5319,7 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
5282
5319
|
init_errors();
|
|
5283
5320
|
|
|
5284
5321
|
// src/providers/minimax/generators/video.ts
|
|
5285
|
-
import
|
|
5322
|
+
import fs33 from "fs";
|
|
5286
5323
|
import path35 from "path";
|
|
5287
5324
|
async function generateMinimaxVideo(opts) {
|
|
5288
5325
|
const {
|
|
@@ -5336,11 +5373,11 @@ async function generateMinimaxVideo(opts) {
|
|
|
5336
5373
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
5337
5374
|
}
|
|
5338
5375
|
const elapsed = (Date.now() - start) / 1e3;
|
|
5339
|
-
const stat =
|
|
5376
|
+
const stat = fs33.statSync(dest);
|
|
5340
5377
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
5341
5378
|
if (output) {
|
|
5342
|
-
|
|
5343
|
-
|
|
5379
|
+
fs33.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
|
|
5380
|
+
fs33.copyFileSync(dest, output);
|
|
5344
5381
|
}
|
|
5345
5382
|
await maybeDownloadThumb(pollResult, dest, {
|
|
5346
5383
|
skip: thumb === false,
|
|
@@ -5402,8 +5439,7 @@ Generated video: ${result.generatedVideo}`);
|
|
|
5402
5439
|
|
|
5403
5440
|
// src/providers/minimax/generators/image.ts
|
|
5404
5441
|
init_errors();
|
|
5405
|
-
|
|
5406
|
-
import fs33 from "fs";
|
|
5442
|
+
import fs34 from "fs";
|
|
5407
5443
|
import path36 from "path";
|
|
5408
5444
|
async function generateMinimaxImage(opts) {
|
|
5409
5445
|
const {
|
|
@@ -5443,7 +5479,7 @@ async function generateMinimaxImage(opts) {
|
|
|
5443
5479
|
const bytes = await fetchBytes(url);
|
|
5444
5480
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
5445
5481
|
const dest = path36.join(outDir, fname);
|
|
5446
|
-
|
|
5482
|
+
fs34.writeFileSync(dest, bytes);
|
|
5447
5483
|
savedFiles.push(dest);
|
|
5448
5484
|
logger?.success(`Saved: ${dest}`);
|
|
5449
5485
|
} catch (e) {
|
|
@@ -5454,8 +5490,8 @@ async function generateMinimaxImage(opts) {
|
|
|
5454
5490
|
}
|
|
5455
5491
|
}
|
|
5456
5492
|
if (output && savedFiles[0]) {
|
|
5457
|
-
|
|
5458
|
-
|
|
5493
|
+
fs34.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
|
|
5494
|
+
fs34.copyFileSync(savedFiles[0], output);
|
|
5459
5495
|
}
|
|
5460
5496
|
return { status: "success", generatedImages: savedFiles, model };
|
|
5461
5497
|
}
|
|
@@ -5494,8 +5530,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
5494
5530
|
|
|
5495
5531
|
// src/providers/minimax/commands/image-to-image.ts
|
|
5496
5532
|
init_errors();
|
|
5497
|
-
|
|
5498
|
-
import fs34 from "fs";
|
|
5533
|
+
import fs35 from "fs";
|
|
5499
5534
|
import path37 from "path";
|
|
5500
5535
|
function registerMinimaxImageToImageCommand(parent) {
|
|
5501
5536
|
parent.command("image-to-image").alias("i2i").description(
|
|
@@ -5552,7 +5587,7 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5552
5587
|
try {
|
|
5553
5588
|
const bytes = await fetchBytes(url);
|
|
5554
5589
|
const dest = path37.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
5555
|
-
|
|
5590
|
+
fs35.writeFileSync(dest, bytes);
|
|
5556
5591
|
saved.push(dest);
|
|
5557
5592
|
logger.success(`Saved: ${dest}`);
|
|
5558
5593
|
} catch (e) {
|
|
@@ -5563,8 +5598,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5563
5598
|
}
|
|
5564
5599
|
}
|
|
5565
5600
|
if (opts.output && saved[0]) {
|
|
5566
|
-
|
|
5567
|
-
|
|
5601
|
+
fs35.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
|
|
5602
|
+
fs35.copyFileSync(saved[0], opts.output);
|
|
5568
5603
|
logger.success(`Copied to: ${opts.output}`);
|
|
5569
5604
|
}
|
|
5570
5605
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -5698,15 +5733,13 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
5698
5733
|
}
|
|
5699
5734
|
|
|
5700
5735
|
// src/providers/openai/openai-audio.ts
|
|
5701
|
-
|
|
5702
|
-
import fs36 from "fs";
|
|
5736
|
+
import fs37 from "fs";
|
|
5703
5737
|
import path39 from "path";
|
|
5704
5738
|
|
|
5705
5739
|
// src/providers/openai/client.ts
|
|
5706
|
-
import
|
|
5740
|
+
import fs36 from "fs";
|
|
5707
5741
|
import path38 from "path";
|
|
5708
5742
|
init_errors();
|
|
5709
|
-
init_http_client();
|
|
5710
5743
|
var DEFAULT_TIMEOUT_MS3 = 24e4;
|
|
5711
5744
|
function requireOpenAIKey() {
|
|
5712
5745
|
const key = resolveKey("OPENAI_API_KEY");
|
|
@@ -5772,8 +5805,8 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
5772
5805
|
}
|
|
5773
5806
|
function fileFromPath(filePath) {
|
|
5774
5807
|
const abs = path38.resolve(filePath);
|
|
5775
|
-
if (!
|
|
5776
|
-
const bytes =
|
|
5808
|
+
if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
5809
|
+
const bytes = fs36.readFileSync(abs);
|
|
5777
5810
|
return new File([new Uint8Array(bytes)], path38.basename(abs));
|
|
5778
5811
|
}
|
|
5779
5812
|
|
|
@@ -5833,11 +5866,11 @@ async function generateOpenAISpeech(opts) {
|
|
|
5833
5866
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
5834
5867
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
5835
5868
|
const dest = path39.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
5836
|
-
|
|
5869
|
+
fs37.writeFileSync(dest, bytes);
|
|
5837
5870
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
5838
5871
|
if (opts.output) {
|
|
5839
|
-
|
|
5840
|
-
|
|
5872
|
+
fs37.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
|
|
5873
|
+
fs37.copyFileSync(dest, opts.output);
|
|
5841
5874
|
}
|
|
5842
5875
|
return dest;
|
|
5843
5876
|
}
|
|
@@ -5860,8 +5893,8 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
5860
5893
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
5861
5894
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
5862
5895
|
const dest = opts.output ? path39.resolve(opts.output) : path39.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
|
|
5863
|
-
|
|
5864
|
-
|
|
5896
|
+
fs37.mkdirSync(path39.dirname(dest), { recursive: true });
|
|
5897
|
+
fs37.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
|
|
5865
5898
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
5866
5899
|
return { path: dest, text };
|
|
5867
5900
|
}
|
|
@@ -5888,7 +5921,7 @@ Generated audio: ${dest}`);
|
|
|
5888
5921
|
}
|
|
5889
5922
|
|
|
5890
5923
|
// src/providers/openai/codex-image-driver.ts
|
|
5891
|
-
import
|
|
5924
|
+
import fs38 from "fs";
|
|
5892
5925
|
import os2 from "os";
|
|
5893
5926
|
import path40 from "path";
|
|
5894
5927
|
import { execa } from "execa";
|
|
@@ -5940,11 +5973,11 @@ async function runCodexImageGeneration(opts) {
|
|
|
5940
5973
|
const runner = opts.runner ?? defaultRunner;
|
|
5941
5974
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
5942
5975
|
const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
|
|
5943
|
-
|
|
5944
|
-
const tmp =
|
|
5976
|
+
fs38.mkdirSync(path40.dirname(dest), { recursive: true });
|
|
5977
|
+
const tmp = fs38.mkdtempSync(path40.join(os2.tmpdir(), "multix-codex-image-"));
|
|
5945
5978
|
const schema = path40.join(tmp, "schema.json");
|
|
5946
5979
|
const result = path40.join(tmp, "result.json");
|
|
5947
|
-
|
|
5980
|
+
fs38.writeFileSync(
|
|
5948
5981
|
schema,
|
|
5949
5982
|
JSON.stringify({
|
|
5950
5983
|
type: "object",
|
|
@@ -5974,7 +6007,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
5974
6007
|
if (res.exitCode !== 0) {
|
|
5975
6008
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
5976
6009
|
}
|
|
5977
|
-
if (!
|
|
6010
|
+
if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
|
|
5978
6011
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
5979
6012
|
}
|
|
5980
6013
|
return { path: dest };
|
|
@@ -5991,8 +6024,7 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
5991
6024
|
|
|
5992
6025
|
// src/providers/openai/openai-image.ts
|
|
5993
6026
|
init_errors();
|
|
5994
|
-
|
|
5995
|
-
import fs38 from "fs";
|
|
6027
|
+
import fs39 from "fs";
|
|
5996
6028
|
import path41 from "path";
|
|
5997
6029
|
function buildImageGenerationBody(opts) {
|
|
5998
6030
|
return {
|
|
@@ -6027,13 +6059,13 @@ function saveOpenAIImages(opts) {
|
|
|
6027
6059
|
const b64 = items[i]?.b64_json;
|
|
6028
6060
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
6029
6061
|
const dest = path41.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
6030
|
-
|
|
6062
|
+
fs39.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
6031
6063
|
saved.push(dest);
|
|
6032
6064
|
opts.logger?.success(`Saved: ${dest}`);
|
|
6033
6065
|
}
|
|
6034
6066
|
if (opts.output && saved[0]) {
|
|
6035
|
-
|
|
6036
|
-
|
|
6067
|
+
fs39.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
|
|
6068
|
+
fs39.copyFileSync(saved[0], opts.output);
|
|
6037
6069
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
6038
6070
|
}
|
|
6039
6071
|
return saved;
|
|
@@ -6231,10 +6263,9 @@ function registerOpenAICommands(program2) {
|
|
|
6231
6263
|
}
|
|
6232
6264
|
|
|
6233
6265
|
// src/providers/openrouter/client.ts
|
|
6234
|
-
import
|
|
6266
|
+
import fs40 from "fs";
|
|
6235
6267
|
import path42 from "path";
|
|
6236
6268
|
init_errors();
|
|
6237
|
-
init_http_client();
|
|
6238
6269
|
|
|
6239
6270
|
// src/providers/openrouter/payload.ts
|
|
6240
6271
|
var IMAGE_ONLY_MODEL_PREFIXES = [
|
|
@@ -6247,15 +6278,15 @@ function resolveModalities(model) {
|
|
|
6247
6278
|
return IMAGE_ONLY_MODEL_PREFIXES.some((p) => lower.startsWith(p)) ? ["image"] : ["image", "text"];
|
|
6248
6279
|
}
|
|
6249
6280
|
function buildOpenRouterHeaders(apiKey) {
|
|
6250
|
-
const
|
|
6281
|
+
const headers2 = {
|
|
6251
6282
|
Authorization: `Bearer ${apiKey}`,
|
|
6252
6283
|
"Content-Type": "application/json"
|
|
6253
6284
|
};
|
|
6254
6285
|
const referer = resolveKey("OPENROUTER_SITE_URL");
|
|
6255
6286
|
const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
|
|
6256
|
-
if (referer)
|
|
6257
|
-
if (title)
|
|
6258
|
-
return
|
|
6287
|
+
if (referer) headers2["HTTP-Referer"] = referer;
|
|
6288
|
+
if (title) headers2["X-Title"] = title;
|
|
6289
|
+
return headers2;
|
|
6259
6290
|
}
|
|
6260
6291
|
function extractImagesFromResponse(data) {
|
|
6261
6292
|
const out = { urls: [] };
|
|
@@ -6398,7 +6429,7 @@ async function generateOpenRouterImage(opts) {
|
|
|
6398
6429
|
try {
|
|
6399
6430
|
const bytes = await fetchBytes(url);
|
|
6400
6431
|
const dest = path42.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
6401
|
-
|
|
6432
|
+
fs40.writeFileSync(dest, bytes);
|
|
6402
6433
|
savedFiles.push(dest);
|
|
6403
6434
|
logger?.success(`Saved: ${dest}`);
|
|
6404
6435
|
} catch (e) {
|
|
@@ -6409,8 +6440,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
6409
6440
|
}
|
|
6410
6441
|
}
|
|
6411
6442
|
if (output && savedFiles[0]) {
|
|
6412
|
-
|
|
6413
|
-
|
|
6443
|
+
fs40.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
|
|
6444
|
+
fs40.writeFileSync(output, fs40.readFileSync(savedFiles[0]));
|
|
6414
6445
|
}
|
|
6415
6446
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
6416
6447
|
}
|
|
@@ -6451,10 +6482,9 @@ Generated ${finalImages.length} image(s):`);
|
|
|
6451
6482
|
}
|
|
6452
6483
|
|
|
6453
6484
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
6454
|
-
import
|
|
6485
|
+
import fs41 from "fs";
|
|
6455
6486
|
import path43 from "path";
|
|
6456
6487
|
init_errors();
|
|
6457
|
-
init_http_client();
|
|
6458
6488
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
6459
6489
|
var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
|
|
6460
6490
|
var HARD_LIMIT_BYTES2 = 8 * 1024 * 1024;
|
|
@@ -6519,7 +6549,7 @@ function parseStrength(raw) {
|
|
|
6519
6549
|
}
|
|
6520
6550
|
async function runImageToImage(opts) {
|
|
6521
6551
|
const apiKey = requireOpenRouterKey();
|
|
6522
|
-
const
|
|
6552
|
+
const headers2 = buildOpenRouterHeaders(apiKey);
|
|
6523
6553
|
const payload = buildI2IPayload({
|
|
6524
6554
|
prompt: opts.prompt,
|
|
6525
6555
|
model: opts.model,
|
|
@@ -6530,7 +6560,7 @@ async function runImageToImage(opts) {
|
|
|
6530
6560
|
const data = await httpJson({
|
|
6531
6561
|
url: OPENROUTER_API_URL,
|
|
6532
6562
|
method: "POST",
|
|
6533
|
-
headers,
|
|
6563
|
+
headers: headers2,
|
|
6534
6564
|
body: payload,
|
|
6535
6565
|
timeoutMs: 24e4
|
|
6536
6566
|
});
|
|
@@ -6546,31 +6576,30 @@ async function runImageToImage(opts) {
|
|
|
6546
6576
|
if (!url) continue;
|
|
6547
6577
|
const bytes = await fetchBytes(url);
|
|
6548
6578
|
const dest = path43.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
6549
|
-
|
|
6579
|
+
fs41.writeFileSync(dest, bytes);
|
|
6550
6580
|
saved.push(dest);
|
|
6551
6581
|
opts.logger.success(`Saved: ${dest}`);
|
|
6552
6582
|
}
|
|
6553
6583
|
if (opts.output && saved[0]) {
|
|
6554
|
-
|
|
6555
|
-
|
|
6584
|
+
fs41.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
|
|
6585
|
+
fs41.copyFileSync(saved[0], opts.output);
|
|
6556
6586
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
6557
6587
|
}
|
|
6558
6588
|
return saved;
|
|
6559
6589
|
}
|
|
6560
6590
|
|
|
6561
6591
|
// src/providers/openrouter/video-client.ts
|
|
6562
|
-
init_http_client();
|
|
6563
6592
|
var OPENROUTER_VIDEOS_URL = "https://openrouter.ai/api/v1/videos";
|
|
6564
6593
|
function authHeaders5(apiKey) {
|
|
6565
|
-
const
|
|
6594
|
+
const headers2 = {
|
|
6566
6595
|
Authorization: `Bearer ${apiKey}`,
|
|
6567
6596
|
"Content-Type": "application/json"
|
|
6568
6597
|
};
|
|
6569
6598
|
const referer = resolveKey("OPENROUTER_SITE_URL");
|
|
6570
6599
|
const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
|
|
6571
|
-
if (referer)
|
|
6572
|
-
if (title)
|
|
6573
|
-
return
|
|
6600
|
+
if (referer) headers2["HTTP-Referer"] = referer;
|
|
6601
|
+
if (title) headers2["X-Title"] = title;
|
|
6602
|
+
return headers2;
|
|
6574
6603
|
}
|
|
6575
6604
|
async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
|
|
6576
6605
|
return httpJson({
|
|
@@ -6598,7 +6627,7 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
6598
6627
|
}
|
|
6599
6628
|
|
|
6600
6629
|
// src/providers/openrouter/commands/video-status.ts
|
|
6601
|
-
import
|
|
6630
|
+
import fs42 from "fs";
|
|
6602
6631
|
import path44 from "path";
|
|
6603
6632
|
async function downloadOpenRouterVideo(opts) {
|
|
6604
6633
|
const { jobId, logger, source } = opts;
|
|
@@ -6614,14 +6643,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
6614
6643
|
logger.error("Empty response body");
|
|
6615
6644
|
process.exit(1);
|
|
6616
6645
|
}
|
|
6617
|
-
|
|
6618
|
-
const ws =
|
|
6646
|
+
fs42.mkdirSync(path44.dirname(dest), { recursive: true });
|
|
6647
|
+
const ws = fs42.createWriteStream(dest);
|
|
6619
6648
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
6620
6649
|
await pipeline2(r.body, ws);
|
|
6621
6650
|
logger.success(`Saved ${dest}`);
|
|
6622
6651
|
if (opts.output) {
|
|
6623
|
-
|
|
6624
|
-
|
|
6652
|
+
fs42.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
|
|
6653
|
+
fs42.copyFileSync(dest, opts.output);
|
|
6625
6654
|
logger.success(`Copied to ${opts.output}`);
|
|
6626
6655
|
}
|
|
6627
6656
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -6807,7 +6836,6 @@ async function checkBinary(binary, versionFlag = "--version") {
|
|
|
6807
6836
|
|
|
6808
6837
|
// src/commands/check-helpers/gemini-ping.ts
|
|
6809
6838
|
init_errors();
|
|
6810
|
-
init_http_client();
|
|
6811
6839
|
var MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
6812
6840
|
async function pingGemini(apiKey) {
|
|
6813
6841
|
try {
|
|
@@ -7040,7 +7068,7 @@ function registerCheckCommand(program2) {
|
|
|
7040
7068
|
|
|
7041
7069
|
// src/commands/doc/convert.ts
|
|
7042
7070
|
init_errors();
|
|
7043
|
-
import
|
|
7071
|
+
import fs43 from "fs";
|
|
7044
7072
|
import path45 from "path";
|
|
7045
7073
|
|
|
7046
7074
|
// src/commands/doc/default-prompt.ts
|
|
@@ -7062,7 +7090,7 @@ async function convertDocuments(opts) {
|
|
|
7062
7090
|
const model = opts.model ?? DOC_MODEL_DEFAULT;
|
|
7063
7091
|
const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
|
|
7064
7092
|
for (const f of inputs) {
|
|
7065
|
-
if (!
|
|
7093
|
+
if (!fs43.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
|
|
7066
7094
|
}
|
|
7067
7095
|
let outputPath;
|
|
7068
7096
|
if (output) {
|
|
@@ -7073,7 +7101,7 @@ async function convertDocuments(opts) {
|
|
|
7073
7101
|
} else {
|
|
7074
7102
|
outputPath = path45.join(getOutputDir(), "document-extraction.md");
|
|
7075
7103
|
}
|
|
7076
|
-
|
|
7104
|
+
fs43.mkdirSync(path45.dirname(outputPath), { recursive: true });
|
|
7077
7105
|
const results = [];
|
|
7078
7106
|
for (let i = 0; i < inputs.length; i++) {
|
|
7079
7107
|
const filePath = inputs[i];
|
|
@@ -7105,7 +7133,7 @@ async function convertDocuments(opts) {
|
|
|
7105
7133
|
if (attempt < 2) {
|
|
7106
7134
|
const wait = 2 ** attempt * 1e3;
|
|
7107
7135
|
logger?.debug(`Retry ${attempt + 1} after ${wait / 1e3}s: ${errorMsg}`);
|
|
7108
|
-
await
|
|
7136
|
+
await sleep4(wait);
|
|
7109
7137
|
}
|
|
7110
7138
|
}
|
|
7111
7139
|
}
|
|
@@ -7137,7 +7165,7 @@ async function convertDocuments(opts) {
|
|
|
7137
7165
|
}
|
|
7138
7166
|
lines.push("\n---\n");
|
|
7139
7167
|
}
|
|
7140
|
-
|
|
7168
|
+
fs43.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
7141
7169
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
7142
7170
|
console.log(`
|
|
7143
7171
|
${"=".repeat(50)}`);
|
|
@@ -7153,7 +7181,7 @@ ${"=".repeat(50)}`);
|
|
|
7153
7181
|
const failed = results.filter((r) => r.status === "error").length;
|
|
7154
7182
|
if (failed > 0) process.exit(1);
|
|
7155
7183
|
}
|
|
7156
|
-
function
|
|
7184
|
+
function sleep4(ms) {
|
|
7157
7185
|
return new Promise((r) => setTimeout(r, ms));
|
|
7158
7186
|
}
|
|
7159
7187
|
|
|
@@ -7185,11 +7213,11 @@ function registerDocCommands(program2) {
|
|
|
7185
7213
|
|
|
7186
7214
|
// src/commands/media/index.ts
|
|
7187
7215
|
init_errors();
|
|
7188
|
-
import
|
|
7216
|
+
import fs46 from "fs";
|
|
7189
7217
|
import path48 from "path";
|
|
7190
7218
|
|
|
7191
7219
|
// src/commands/media/batch.ts
|
|
7192
|
-
import
|
|
7220
|
+
import fs44 from "fs";
|
|
7193
7221
|
import path46 from "path";
|
|
7194
7222
|
|
|
7195
7223
|
// src/commands/media/detect-type.ts
|
|
@@ -7386,8 +7414,8 @@ async function batchOptimize(opts) {
|
|
|
7386
7414
|
verbose,
|
|
7387
7415
|
logger
|
|
7388
7416
|
} = opts;
|
|
7389
|
-
|
|
7390
|
-
const entries =
|
|
7417
|
+
fs44.mkdirSync(outputDir, { recursive: true });
|
|
7418
|
+
const entries = fs44.readdirSync(inputDir);
|
|
7391
7419
|
const mediaFiles = entries.filter((f) => {
|
|
7392
7420
|
const ext = path46.extname(f).toLowerCase();
|
|
7393
7421
|
return ALL_EXTS.has(ext);
|
|
@@ -7439,7 +7467,7 @@ async function batchOptimize(opts) {
|
|
|
7439
7467
|
|
|
7440
7468
|
// src/commands/media/split-video.ts
|
|
7441
7469
|
init_errors();
|
|
7442
|
-
import
|
|
7470
|
+
import fs45 from "fs";
|
|
7443
7471
|
import path47 from "path";
|
|
7444
7472
|
async function splitVideo(opts) {
|
|
7445
7473
|
await assertFfmpeg();
|
|
@@ -7451,7 +7479,7 @@ async function splitVideo(opts) {
|
|
|
7451
7479
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
7452
7480
|
return [input];
|
|
7453
7481
|
}
|
|
7454
|
-
|
|
7482
|
+
fs45.mkdirSync(outputDir, { recursive: true });
|
|
7455
7483
|
const ext = path47.extname(input) || ".mp4";
|
|
7456
7484
|
const baseName = path47.basename(input, ext);
|
|
7457
7485
|
const outputPattern = path47.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
@@ -7474,7 +7502,7 @@ async function splitVideo(opts) {
|
|
|
7474
7502
|
const files = [];
|
|
7475
7503
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
7476
7504
|
const fname = path47.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
7477
|
-
if (
|
|
7505
|
+
if (fs45.existsSync(fname)) files.push(fname);
|
|
7478
7506
|
}
|
|
7479
7507
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
7480
7508
|
return files;
|
|
@@ -7488,8 +7516,8 @@ function registerMediaCommands(program2) {
|
|
|
7488
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(
|
|
7489
7517
|
async (opts) => {
|
|
7490
7518
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7491
|
-
if (!
|
|
7492
|
-
|
|
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 });
|
|
7493
7521
|
const ext = path48.extname(opts.input).toLowerCase();
|
|
7494
7522
|
const kind = extToKind(ext);
|
|
7495
7523
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
@@ -7530,7 +7558,7 @@ function registerMediaCommands(program2) {
|
|
|
7530
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(
|
|
7531
7559
|
async (opts) => {
|
|
7532
7560
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7533
|
-
if (!
|
|
7561
|
+
if (!fs46.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
7534
7562
|
const chunks = await splitVideo({
|
|
7535
7563
|
input: opts.input,
|
|
7536
7564
|
outputDir: opts.outputDir,
|
|
@@ -7546,7 +7574,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
7546
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(
|
|
7547
7575
|
async (opts) => {
|
|
7548
7576
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7549
|
-
if (!
|
|
7577
|
+
if (!fs46.existsSync(opts.inputDir))
|
|
7550
7578
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
7551
7579
|
const result = await batchOptimize({
|
|
7552
7580
|
inputDir: opts.inputDir,
|
|
@@ -7569,7 +7597,6 @@ Processed: ${result.succeeded}/${result.total} files`);
|
|
|
7569
7597
|
|
|
7570
7598
|
// src/commands/update.ts
|
|
7571
7599
|
init_errors();
|
|
7572
|
-
init_http_client();
|
|
7573
7600
|
import { createRequire } from "module";
|
|
7574
7601
|
import { execa as execa5 } from "execa";
|
|
7575
7602
|
|
|
@@ -7734,11 +7761,6 @@ function registerCommands(program2) {
|
|
|
7734
7761
|
// src/core/index.ts
|
|
7735
7762
|
init_errors();
|
|
7736
7763
|
init_redact();
|
|
7737
|
-
init_http_client();
|
|
7738
|
-
init_fetch_json();
|
|
7739
|
-
|
|
7740
|
-
// src/core/async-poll.ts
|
|
7741
|
-
init_errors();
|
|
7742
7764
|
|
|
7743
7765
|
// src/cli.ts
|
|
7744
7766
|
loadEnv();
|