@mrgoonie/multix 0.2.0 → 0.3.0
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/README.md +40 -2
- package/dist/cli.js +645 -254
- package/package.json +1 -1
- package/skill/SKILL.md +38 -3
package/dist/cli.js
CHANGED
|
@@ -414,9 +414,9 @@ var BytePlusClient = class {
|
|
|
414
414
|
}
|
|
415
415
|
apiKey;
|
|
416
416
|
baseUrl;
|
|
417
|
-
async request(
|
|
417
|
+
async request(path46, opts = {}) {
|
|
418
418
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
419
|
-
const url = new URL(base +
|
|
419
|
+
const url = new URL(base + path46);
|
|
420
420
|
if (opts.query) {
|
|
421
421
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
422
422
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
@@ -434,14 +434,14 @@ var BytePlusClient = class {
|
|
|
434
434
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
435
435
|
});
|
|
436
436
|
}
|
|
437
|
-
get(
|
|
438
|
-
return this.request(
|
|
437
|
+
get(path46, query, logger) {
|
|
438
|
+
return this.request(path46, { method: "GET", query, logger });
|
|
439
439
|
}
|
|
440
|
-
post(
|
|
441
|
-
return this.request(
|
|
440
|
+
post(path46, body, logger) {
|
|
441
|
+
return this.request(path46, { method: "POST", body, logger });
|
|
442
442
|
}
|
|
443
|
-
delete(
|
|
444
|
-
return this.request(
|
|
443
|
+
delete(path46, logger) {
|
|
444
|
+
return this.request(path46, { method: "DELETE", logger });
|
|
445
445
|
}
|
|
446
446
|
};
|
|
447
447
|
function createBytePlusClient() {
|
|
@@ -990,10 +990,10 @@ function toRef(kind, resolved, role) {
|
|
|
990
990
|
return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
|
|
991
991
|
}
|
|
992
992
|
async function resolveRefSpec(kind, spec, logger) {
|
|
993
|
-
const { path:
|
|
994
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
995
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
996
|
-
return toRef(kind, await resolveAudioInput(
|
|
993
|
+
const { path: path46, role } = parseRefSpec(spec);
|
|
994
|
+
if (kind === "image") return toRef(kind, await resolveImageInput(path46, logger), role);
|
|
995
|
+
if (kind === "video") return toRef(kind, await resolveVideoInput(path46, logger), role);
|
|
996
|
+
return toRef(kind, await resolveAudioInput(path46, logger), role);
|
|
997
997
|
}
|
|
998
998
|
async function resolveRefSpecs(specs, logger) {
|
|
999
999
|
const out = [];
|
|
@@ -1131,11 +1131,11 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
1131
1131
|
console.log(JSON.stringify(status, null, 2));
|
|
1132
1132
|
if (opts.download && status.status === "succeeded") {
|
|
1133
1133
|
const content = status.content;
|
|
1134
|
-
const
|
|
1134
|
+
const videoUrl2 = content?.video_url;
|
|
1135
1135
|
const fileUrl = content?.file_url;
|
|
1136
|
-
if (
|
|
1136
|
+
if (videoUrl2) {
|
|
1137
1137
|
const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
|
|
1138
|
-
await downloadVideo(
|
|
1138
|
+
await downloadVideo(videoUrl2, outPath, logger);
|
|
1139
1139
|
await maybeDownloadThumb(status, outPath, {
|
|
1140
1140
|
skip: opts.thumb === false,
|
|
1141
1141
|
copyTo: opts.output,
|
|
@@ -1251,9 +1251,375 @@ function registerBytePlusCommands(program2) {
|
|
|
1251
1251
|
registerBytePlusStatusCommand(byteplus);
|
|
1252
1252
|
}
|
|
1253
1253
|
|
|
1254
|
-
// src/providers/
|
|
1254
|
+
// src/providers/cloudflare/commands/generate-speech.ts
|
|
1255
|
+
init_errors();
|
|
1256
|
+
|
|
1257
|
+
// src/providers/cloudflare/client.ts
|
|
1258
|
+
init_errors();
|
|
1259
|
+
|
|
1260
|
+
// src/providers/cloudflare/models.ts
|
|
1261
|
+
init_errors();
|
|
1262
|
+
var CLOUDFLARE_IMAGE_MODEL = "@cf/black-forest-labs/flux-1-schnell";
|
|
1263
|
+
var CLOUDFLARE_TTS_MODEL = "@cf/myshell-ai/melotts";
|
|
1264
|
+
var CLOUDFLARE_VIDEO_MODEL = "prunaai/p-video";
|
|
1265
|
+
function required(name) {
|
|
1266
|
+
const value = resolveKey(name)?.trim();
|
|
1267
|
+
if (!value) throw new ConfigError(`${name} is required for Cloudflare media generation`);
|
|
1268
|
+
return value;
|
|
1269
|
+
}
|
|
1270
|
+
function getCloudflareModel(kind, requested) {
|
|
1271
|
+
const expected = kind === "image" ? CLOUDFLARE_IMAGE_MODEL : CLOUDFLARE_TTS_MODEL;
|
|
1272
|
+
const overrideName = kind === "image" ? "CLOUDFLARE_AI_IMAGE_MODEL" : "CLOUDFLARE_AI_TTS_MODEL";
|
|
1273
|
+
const model = requested ?? resolveKey(overrideName) ?? expected;
|
|
1274
|
+
if (model !== expected) {
|
|
1275
|
+
throw new ValidationError(`Unsupported Cloudflare ${kind} model: ${model}`);
|
|
1276
|
+
}
|
|
1277
|
+
return model;
|
|
1278
|
+
}
|
|
1279
|
+
function validateImageSteps(value) {
|
|
1280
|
+
if (!/^\d+$/.test(value)) {
|
|
1281
|
+
throw new ValidationError("--steps must be an integer from 1 to 8 for flux-1-schnell");
|
|
1282
|
+
}
|
|
1283
|
+
const steps = Number.parseInt(value, 10);
|
|
1284
|
+
if (!Number.isInteger(steps) || steps < 1 || steps > 8) {
|
|
1285
|
+
throw new ValidationError("--steps must be an integer from 1 to 8 for flux-1-schnell");
|
|
1286
|
+
}
|
|
1287
|
+
return steps;
|
|
1288
|
+
}
|
|
1289
|
+
function requireCloudflareConfig() {
|
|
1290
|
+
return {
|
|
1291
|
+
accountId: required("CLOUDFLARE_ACCOUNT_ID"),
|
|
1292
|
+
apiToken: required("CLOUDFLARE_API_TOKEN"),
|
|
1293
|
+
gatewayId: resolveKey("CLOUDFLARE_AI_GATEWAY_ID")?.trim() || void 0
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function requireCloudflareVideoConfig() {
|
|
1297
|
+
const config = requireCloudflareConfig();
|
|
1298
|
+
if (!config.gatewayId) {
|
|
1299
|
+
throw new ConfigError("CLOUDFLARE_AI_GATEWAY_ID is required for Cloudflare video generation");
|
|
1300
|
+
}
|
|
1301
|
+
return {
|
|
1302
|
+
...config,
|
|
1303
|
+
gatewayId: config.gatewayId,
|
|
1304
|
+
replicateApiToken: required("REPLICATE_API_TOKEN")
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// src/providers/cloudflare/client.ts
|
|
1309
|
+
function collectPayloadHeader() {
|
|
1310
|
+
const envName = ["CLOUDFLARE", "AI", "GATEWAY", "COLLECT", "LOG", "PAYLOAD"].join("_");
|
|
1311
|
+
return resolveKey(envName) === "true" ? "true" : "false";
|
|
1312
|
+
}
|
|
1313
|
+
function gatewayHeaders(config) {
|
|
1314
|
+
return {
|
|
1315
|
+
Authorization: `Bearer ${config.apiToken}`,
|
|
1316
|
+
"Content-Type": "application/json",
|
|
1317
|
+
"cf-aig-gateway-id": config.gatewayId ?? "",
|
|
1318
|
+
"cf-aig-collect-log-payload": collectPayloadHeader()
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function providerFailure(status) {
|
|
1322
|
+
return new ProviderError(`Cloudflare request failed with HTTP ${status}`, "Cloudflare");
|
|
1323
|
+
}
|
|
1324
|
+
async function readJson(response) {
|
|
1325
|
+
if (!response.ok) throw providerFailure(response.status);
|
|
1326
|
+
try {
|
|
1327
|
+
return await response.json();
|
|
1328
|
+
} catch {
|
|
1329
|
+
throw new ProviderError("Cloudflare returned an invalid JSON response", "Cloudflare");
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
function nativeUrl(accountId, model) {
|
|
1333
|
+
return `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountId)}/ai/run/${model}`;
|
|
1334
|
+
}
|
|
1335
|
+
function gatewayUrl(accountId) {
|
|
1336
|
+
return `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountId)}/ai/run`;
|
|
1337
|
+
}
|
|
1338
|
+
async function runCloudflareJson(model, input) {
|
|
1339
|
+
const config = requireCloudflareConfig();
|
|
1340
|
+
const useGateway = Boolean(config.gatewayId);
|
|
1341
|
+
const response = await globalThis.fetch(
|
|
1342
|
+
useGateway ? gatewayUrl(config.accountId) : nativeUrl(config.accountId, model),
|
|
1343
|
+
{
|
|
1344
|
+
method: "POST",
|
|
1345
|
+
headers: useGateway ? gatewayHeaders(config) : { Authorization: `Bearer ${config.apiToken}`, "Content-Type": "application/json" },
|
|
1346
|
+
body: JSON.stringify(useGateway ? { model, input } : input)
|
|
1347
|
+
}
|
|
1348
|
+
);
|
|
1349
|
+
const payload = await readJson(response);
|
|
1350
|
+
if (payload.success === false || payload.result === void 0) {
|
|
1351
|
+
throw new ProviderError("Cloudflare returned no usable result", "Cloudflare");
|
|
1352
|
+
}
|
|
1353
|
+
return payload.result;
|
|
1354
|
+
}
|
|
1355
|
+
async function runCloudflareBytes(model, input) {
|
|
1356
|
+
const config = requireCloudflareConfig();
|
|
1357
|
+
const useGateway = Boolean(config.gatewayId);
|
|
1358
|
+
const response = await globalThis.fetch(
|
|
1359
|
+
useGateway ? gatewayUrl(config.accountId) : nativeUrl(config.accountId, model),
|
|
1360
|
+
{
|
|
1361
|
+
method: "POST",
|
|
1362
|
+
headers: useGateway ? gatewayHeaders(config) : { Authorization: `Bearer ${config.apiToken}`, "Content-Type": "application/json" },
|
|
1363
|
+
body: JSON.stringify(useGateway ? { model, input } : input)
|
|
1364
|
+
}
|
|
1365
|
+
);
|
|
1366
|
+
if (!response.ok) throw providerFailure(response.status);
|
|
1367
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1368
|
+
if (!contentType.includes("audio/mpeg")) {
|
|
1369
|
+
throw new ProviderError("Cloudflare did not return MPEG audio", "Cloudflare");
|
|
1370
|
+
}
|
|
1371
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1372
|
+
}
|
|
1373
|
+
function videoUrl(config, suffix = "") {
|
|
1374
|
+
return `https://gateway.ai.cloudflare.com/v1/${encodeURIComponent(config.accountId)}/${encodeURIComponent(config.gatewayId)}/replicate/predictions${suffix}`;
|
|
1375
|
+
}
|
|
1376
|
+
function videoHeaders(config, wait = false) {
|
|
1377
|
+
return {
|
|
1378
|
+
Authorization: `Bearer ${config.replicateApiToken}`,
|
|
1379
|
+
"cf-aig-authorization": `Bearer ${config.apiToken}`,
|
|
1380
|
+
"cf-aig-collect-log-payload": collectPayloadHeader(),
|
|
1381
|
+
"Content-Type": "application/json",
|
|
1382
|
+
...wait ? { Prefer: "wait" } : {}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
function prediction(value) {
|
|
1386
|
+
if (!value || typeof value !== "object") {
|
|
1387
|
+
throw new ProviderError("Cloudflare returned an invalid video prediction", "Cloudflare");
|
|
1388
|
+
}
|
|
1389
|
+
const result = value;
|
|
1390
|
+
if (typeof result.id !== "string" || typeof result.status !== "string") {
|
|
1391
|
+
throw new ProviderError("Cloudflare returned an invalid video prediction", "Cloudflare");
|
|
1392
|
+
}
|
|
1393
|
+
if (result.output !== void 0 && typeof result.output !== "string" && (!Array.isArray(result.output) || !result.output.every((item) => typeof item === "string"))) {
|
|
1394
|
+
throw new ProviderError("Cloudflare returned an invalid video output", "Cloudflare");
|
|
1395
|
+
}
|
|
1396
|
+
return { id: result.id, status: result.status, output: result.output };
|
|
1397
|
+
}
|
|
1398
|
+
async function createVideoPrediction(input, wait = false) {
|
|
1399
|
+
const config = requireCloudflareVideoConfig();
|
|
1400
|
+
const response = await globalThis.fetch(videoUrl(config), {
|
|
1401
|
+
method: "POST",
|
|
1402
|
+
headers: videoHeaders(config, wait),
|
|
1403
|
+
body: JSON.stringify({ version: CLOUDFLARE_VIDEO_MODEL, input })
|
|
1404
|
+
});
|
|
1405
|
+
return prediction(await readJson(response));
|
|
1406
|
+
}
|
|
1407
|
+
async function getVideoPrediction(id) {
|
|
1408
|
+
const config = requireCloudflareVideoConfig();
|
|
1409
|
+
const response = await globalThis.fetch(videoUrl(config, `/${encodeURIComponent(id)}`), {
|
|
1410
|
+
headers: videoHeaders(config)
|
|
1411
|
+
});
|
|
1412
|
+
return prediction(await readJson(response));
|
|
1413
|
+
}
|
|
1414
|
+
async function downloadVideo2(url) {
|
|
1415
|
+
let response;
|
|
1416
|
+
try {
|
|
1417
|
+
response = await globalThis.fetch(url);
|
|
1418
|
+
} catch {
|
|
1419
|
+
throw new ProviderError("Cloudflare video download could not be started", "Cloudflare");
|
|
1420
|
+
}
|
|
1421
|
+
if (!response.ok)
|
|
1422
|
+
throw new ProviderError(
|
|
1423
|
+
`Cloudflare video download failed with HTTP ${response.status}`,
|
|
1424
|
+
"Cloudflare"
|
|
1425
|
+
);
|
|
1426
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/providers/cloudflare/media-output.ts
|
|
1430
|
+
init_errors();
|
|
1255
1431
|
import fs7 from "fs";
|
|
1256
1432
|
import path12 from "path";
|
|
1433
|
+
function destination(filename, output) {
|
|
1434
|
+
return output ? path12.resolve(output) : path12.join(getOutputDir(), filename);
|
|
1435
|
+
}
|
|
1436
|
+
function saveBytes(bytes, filename, output) {
|
|
1437
|
+
const dest = destination(filename, output);
|
|
1438
|
+
fs7.mkdirSync(path12.dirname(dest), { recursive: true });
|
|
1439
|
+
fs7.writeFileSync(dest, bytes);
|
|
1440
|
+
return dest;
|
|
1441
|
+
}
|
|
1442
|
+
function saveBase64Image(image, output) {
|
|
1443
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(image) || image.length === 0 || image.length % 4 !== 0) {
|
|
1444
|
+
throw new ProviderError("Cloudflare returned an invalid image result", "Cloudflare");
|
|
1445
|
+
}
|
|
1446
|
+
let bytes;
|
|
1447
|
+
try {
|
|
1448
|
+
bytes = Buffer.from(image, "base64");
|
|
1449
|
+
} catch {
|
|
1450
|
+
throw new ProviderError("Cloudflare returned an invalid image result", "Cloudflare");
|
|
1451
|
+
}
|
|
1452
|
+
if (bytes.length === 0)
|
|
1453
|
+
throw new ProviderError("Cloudflare returned an empty image result", "Cloudflare");
|
|
1454
|
+
return saveBytes(bytes, `cloudflare_image_${Date.now()}.jpg`, output);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// src/providers/cloudflare/commands/generate-speech.ts
|
|
1458
|
+
function registerCloudflareSpeechCommand(parent) {
|
|
1459
|
+
parent.command("generate-speech").description("Generate MPEG speech with Cloudflare Workers AI MeloTTS").requiredOption("--text <text>", "Text to speak").option("--lang <code>", "MeloTTS language code").option("--model <id>", "Cloudflare TTS model").option("--output <path>", "Save MP3 at this path").option("-v, --verbose", "Verbose logging").action(
|
|
1460
|
+
async (opts) => {
|
|
1461
|
+
if (!opts.text.trim()) throw new ValidationError("--text must not be empty");
|
|
1462
|
+
if (opts.lang !== void 0 && !opts.lang.trim())
|
|
1463
|
+
throw new ValidationError("--lang must not be empty");
|
|
1464
|
+
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
1465
|
+
const model = getCloudflareModel("speech", opts.model);
|
|
1466
|
+
const input = { prompt: opts.text };
|
|
1467
|
+
if (opts.lang) input.lang = opts.lang;
|
|
1468
|
+
logger.debug(`Cloudflare speech model=${model}`);
|
|
1469
|
+
const bytes = await runCloudflareBytes(model, input);
|
|
1470
|
+
const destination2 = saveBytes(bytes, `cloudflare_speech_${Date.now()}.mp3`, opts.output);
|
|
1471
|
+
logger.success(`Saved ${destination2}`);
|
|
1472
|
+
console.log(destination2);
|
|
1473
|
+
}
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// src/providers/cloudflare/commands/generate-video.ts
|
|
1478
|
+
init_errors();
|
|
1479
|
+
|
|
1480
|
+
// src/providers/cloudflare/commands/video-helpers.ts
|
|
1481
|
+
init_errors();
|
|
1482
|
+
function positiveInteger(value, flag) {
|
|
1483
|
+
if (!/^\d+$/.test(value)) throw new ValidationError(`${flag} must be a positive integer`);
|
|
1484
|
+
const parsed = Number.parseInt(value, 10);
|
|
1485
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
1486
|
+
throw new ValidationError(`${flag} must be a positive integer`);
|
|
1487
|
+
return parsed;
|
|
1488
|
+
}
|
|
1489
|
+
function outputUrl(prediction2) {
|
|
1490
|
+
if (typeof prediction2.output === "string") return prediction2.output;
|
|
1491
|
+
return prediction2.output?.find((value) => typeof value === "string");
|
|
1492
|
+
}
|
|
1493
|
+
async function saveCompletedVideo(prediction2, output) {
|
|
1494
|
+
const url = outputUrl(prediction2);
|
|
1495
|
+
if (!url)
|
|
1496
|
+
throw new ProviderError("Cloudflare completed the video without an output", "Cloudflare");
|
|
1497
|
+
const bytes = await downloadVideo2(url);
|
|
1498
|
+
return saveBytes(bytes, `cloudflare_video_${prediction2.id}.mp4`, output);
|
|
1499
|
+
}
|
|
1500
|
+
async function waitForVideo(prediction2, timeoutMs, onTick) {
|
|
1501
|
+
const deadline = Date.now() + timeoutMs;
|
|
1502
|
+
let current = prediction2;
|
|
1503
|
+
while (!isTerminalVideoStatus(current.status)) {
|
|
1504
|
+
if (Date.now() >= deadline)
|
|
1505
|
+
throw new ProviderError("Cloudflare video polling timed out", "Cloudflare");
|
|
1506
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
1507
|
+
current = await getVideoPrediction(current.id);
|
|
1508
|
+
onTick?.(current.status);
|
|
1509
|
+
}
|
|
1510
|
+
if (current.status !== "succeeded")
|
|
1511
|
+
throw new ProviderError("Cloudflare video generation failed", "Cloudflare");
|
|
1512
|
+
return current;
|
|
1513
|
+
}
|
|
1514
|
+
function isTerminalVideoStatus(status) {
|
|
1515
|
+
return status === "succeeded" || status === "failed" || status === "canceled" || status === "cancelled" || status === "aborted";
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/providers/cloudflare/commands/generate-video.ts
|
|
1519
|
+
function registerCloudflareVideoCommand(parent) {
|
|
1520
|
+
parent.command("generate-video").description("Create a prunaai/p-video prediction through Cloudflare AI Gateway").requiredOption("--prompt <text>", "Video prompt").option("--duration <seconds>", "Video duration in seconds", "5").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option("--resolution <value>", "Resolution", "720p").option("--fps <n>", "Frames per second", "24").option("--wait", "Poll until the prediction succeeds or fails").option("--wait-timeout <ms>", "Polling timeout in ms", "600000").option("--download", "Download the completed video (implies --wait)").option("--output <path>", "Save video at this path").option("-v, --verbose", "Verbose logging").action(
|
|
1521
|
+
async (opts) => {
|
|
1522
|
+
if (!opts.prompt.trim()) throw new ValidationError("--prompt must not be empty");
|
|
1523
|
+
if (!opts.aspectRatio.trim()) throw new ValidationError("--aspect-ratio must not be empty");
|
|
1524
|
+
if (!opts.resolution.trim()) throw new ValidationError("--resolution must not be empty");
|
|
1525
|
+
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
1526
|
+
const input = {
|
|
1527
|
+
prompt: opts.prompt,
|
|
1528
|
+
duration: positiveInteger(opts.duration, "--duration"),
|
|
1529
|
+
aspect_ratio: opts.aspectRatio,
|
|
1530
|
+
resolution: opts.resolution,
|
|
1531
|
+
fps: positiveInteger(opts.fps, "--fps")
|
|
1532
|
+
};
|
|
1533
|
+
const shouldWait = Boolean(opts.wait || opts.download);
|
|
1534
|
+
const prediction2 = await createVideoPrediction(input, shouldWait);
|
|
1535
|
+
logger.success(`Video job ${prediction2.id} (${prediction2.status})`);
|
|
1536
|
+
if (!shouldWait) {
|
|
1537
|
+
console.log(prediction2.id);
|
|
1538
|
+
console.error(`Poll with: multix cloudflare video-status ${prediction2.id}`);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
const completed = prediction2.status === "succeeded" ? prediction2 : await waitForVideo(
|
|
1542
|
+
prediction2,
|
|
1543
|
+
positiveInteger(opts.waitTimeout, "--wait-timeout"),
|
|
1544
|
+
(status) => logger.debug(`Video status: ${status}`)
|
|
1545
|
+
);
|
|
1546
|
+
const destination2 = await saveCompletedVideo(completed, opts.output);
|
|
1547
|
+
logger.success(`Saved ${destination2}`);
|
|
1548
|
+
console.log(destination2);
|
|
1549
|
+
}
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/providers/cloudflare/commands/generate.ts
|
|
1554
|
+
init_errors();
|
|
1555
|
+
function registerCloudflareGenerateCommand(parent) {
|
|
1556
|
+
parent.command("generate").description("Generate an image with Cloudflare Workers AI FLUX.1 Schnell").requiredOption("--prompt <text>", "Image prompt").option("--model <id>", "Cloudflare image model").option("--steps <n>", "Inference steps (1-8)", "4").option("--seed <n>", "Optional non-negative integer seed").option("--output <path>", "Save image at this path").option("-v, --verbose", "Verbose logging").action(
|
|
1557
|
+
async (opts) => {
|
|
1558
|
+
if (!opts.prompt.trim()) throw new ValidationError("--prompt must not be empty");
|
|
1559
|
+
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
1560
|
+
const model = getCloudflareModel("image", opts.model);
|
|
1561
|
+
const steps = validateImageSteps(opts.steps);
|
|
1562
|
+
const input = { prompt: opts.prompt, steps };
|
|
1563
|
+
if (opts.seed !== void 0) {
|
|
1564
|
+
if (!/^\d+$/.test(opts.seed)) {
|
|
1565
|
+
throw new ValidationError("--seed must be a non-negative integer");
|
|
1566
|
+
}
|
|
1567
|
+
const seed = Number.parseInt(opts.seed, 10);
|
|
1568
|
+
if (!Number.isInteger(seed) || seed < 0) {
|
|
1569
|
+
throw new ValidationError("--seed must be a non-negative integer");
|
|
1570
|
+
}
|
|
1571
|
+
input.seed = seed;
|
|
1572
|
+
}
|
|
1573
|
+
logger.debug(`Cloudflare image model=${model} steps=${steps}`);
|
|
1574
|
+
const result = await runCloudflareJson(model, input);
|
|
1575
|
+
if (typeof result.image !== "string") {
|
|
1576
|
+
throw new ProviderError("Cloudflare returned no image result", "Cloudflare");
|
|
1577
|
+
}
|
|
1578
|
+
const destination2 = saveBase64Image(result.image, opts.output);
|
|
1579
|
+
logger.success(`Saved ${destination2}`);
|
|
1580
|
+
console.log(destination2);
|
|
1581
|
+
}
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/providers/cloudflare/commands/video-status.ts
|
|
1586
|
+
function registerCloudflareVideoStatusCommand(parent) {
|
|
1587
|
+
parent.command("video-status <id>").description("Check, wait for, or download a Cloudflare AI Gateway video prediction").option("--wait", "Poll until the prediction succeeds or fails").option("--wait-timeout <ms>", "Polling timeout in ms", "600000").option("--download", "Download the completed video (implies --wait)").option("--output <path>", "Save video at this path").option("-v, --verbose", "Verbose logging").action(
|
|
1588
|
+
async (id, opts) => {
|
|
1589
|
+
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
1590
|
+
const shouldWait = Boolean(opts.wait || opts.download);
|
|
1591
|
+
const prediction2 = await getVideoPrediction(id);
|
|
1592
|
+
const completed = shouldWait && prediction2.status !== "succeeded" ? await waitForVideo(
|
|
1593
|
+
prediction2,
|
|
1594
|
+
positiveInteger(opts.waitTimeout, "--wait-timeout"),
|
|
1595
|
+
(status) => logger.debug(`Video status: ${status}`)
|
|
1596
|
+
) : prediction2;
|
|
1597
|
+
console.log(`${completed.id}: ${completed.status}`);
|
|
1598
|
+
if (opts.download) {
|
|
1599
|
+
if (completed.status !== "succeeded") {
|
|
1600
|
+
logger.warn(`Cannot download while status is ${completed.status}`);
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
const destination2 = await saveCompletedVideo(completed, opts.output);
|
|
1604
|
+
logger.success(`Saved ${destination2}`);
|
|
1605
|
+
console.log(destination2);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/providers/cloudflare/commands/index.ts
|
|
1612
|
+
function registerCloudflareCommands(program2) {
|
|
1613
|
+
const cloudflare = program2.command("cloudflare").description("Cloudflare Workers AI image and speech, plus AI Gateway video generation");
|
|
1614
|
+
registerCloudflareGenerateCommand(cloudflare);
|
|
1615
|
+
registerCloudflareSpeechCommand(cloudflare);
|
|
1616
|
+
registerCloudflareVideoCommand(cloudflare);
|
|
1617
|
+
registerCloudflareVideoStatusCommand(cloudflare);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// src/providers/elevenlabs/client.ts
|
|
1621
|
+
import fs8 from "fs";
|
|
1622
|
+
import path13 from "path";
|
|
1257
1623
|
init_errors();
|
|
1258
1624
|
init_http_client();
|
|
1259
1625
|
|
|
@@ -1415,10 +1781,10 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
|
|
|
1415
1781
|
return res.json();
|
|
1416
1782
|
}
|
|
1417
1783
|
function readFileAsBlob(filePath, fieldFilename) {
|
|
1418
|
-
const abs =
|
|
1419
|
-
if (!
|
|
1420
|
-
const buf =
|
|
1421
|
-
const name = fieldFilename ??
|
|
1784
|
+
const abs = path13.resolve(filePath);
|
|
1785
|
+
if (!fs8.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
1786
|
+
const buf = fs8.readFileSync(abs);
|
|
1787
|
+
const name = fieldFilename ?? path13.basename(abs);
|
|
1422
1788
|
return new File([new Uint8Array(buf)], name);
|
|
1423
1789
|
}
|
|
1424
1790
|
async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
|
|
@@ -1439,15 +1805,15 @@ async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
|
|
|
1439
1805
|
}
|
|
1440
1806
|
throw new ProviderError(`${label} timed out after ${timeoutMs / 1e3}s`, "elevenlabs");
|
|
1441
1807
|
}
|
|
1442
|
-
function
|
|
1808
|
+
function saveBytes2(opts) {
|
|
1443
1809
|
const { bytes, task, ext, outputCopy, logger } = opts;
|
|
1444
1810
|
const dir = getOutputDir();
|
|
1445
|
-
const dest =
|
|
1446
|
-
|
|
1811
|
+
const dest = path13.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
|
|
1812
|
+
fs8.writeFileSync(dest, bytes);
|
|
1447
1813
|
logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
1448
1814
|
if (outputCopy) {
|
|
1449
|
-
|
|
1450
|
-
|
|
1815
|
+
fs8.mkdirSync(path13.dirname(path13.resolve(outputCopy)), { recursive: true });
|
|
1816
|
+
fs8.copyFileSync(dest, outputCopy);
|
|
1451
1817
|
}
|
|
1452
1818
|
return dest;
|
|
1453
1819
|
}
|
|
@@ -1486,14 +1852,14 @@ ${models.length} models`);
|
|
|
1486
1852
|
}
|
|
1487
1853
|
|
|
1488
1854
|
// src/providers/elevenlabs/commands/align.ts
|
|
1489
|
-
import
|
|
1490
|
-
import
|
|
1855
|
+
import fs9 from "fs";
|
|
1856
|
+
import path14 from "path";
|
|
1491
1857
|
function registerAlignCommand(parent) {
|
|
1492
1858
|
parent.command("align").description("Forced alignment of audio to a transcript (returns word/char timestamps)").requiredOption("--input <path>", "Source audio file").requiredOption("--text <str>", "Transcript text (or pass --text-file)").option("--text-file <path>", "Read transcript from file").option("--output <path>", "Save JSON to this path (default: multix-output)").option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
1493
1859
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
1494
1860
|
const apiKey = requireElevenLabsKey();
|
|
1495
1861
|
let text = opts.text;
|
|
1496
|
-
if (opts.textFile) text =
|
|
1862
|
+
if (opts.textFile) text = fs9.readFileSync(path14.resolve(opts.textFile), "utf8");
|
|
1497
1863
|
const form = new FormData();
|
|
1498
1864
|
form.append("file", readFileAsBlob(opts.input));
|
|
1499
1865
|
form.append("text", text);
|
|
@@ -1501,9 +1867,9 @@ function registerAlignCommand(parent) {
|
|
|
1501
1867
|
const data = await apiPostMultipart("forced-alignment", form, apiKey, {
|
|
1502
1868
|
timeoutMs: 6e5
|
|
1503
1869
|
});
|
|
1504
|
-
const dest = opts.output ?
|
|
1505
|
-
|
|
1506
|
-
|
|
1870
|
+
const dest = opts.output ? path14.resolve(opts.output) : path14.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
|
|
1871
|
+
fs9.mkdirSync(path14.dirname(dest), { recursive: true });
|
|
1872
|
+
fs9.writeFileSync(dest, JSON.stringify(data, null, 2));
|
|
1507
1873
|
logger.success(`Saved alignment: ${dest}`);
|
|
1508
1874
|
if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
|
|
1509
1875
|
});
|
|
@@ -1546,8 +1912,8 @@ function registerCloneCommand(parent) {
|
|
|
1546
1912
|
// src/providers/elevenlabs/commands/dub.ts
|
|
1547
1913
|
init_errors();
|
|
1548
1914
|
init_http_client();
|
|
1549
|
-
import
|
|
1550
|
-
import
|
|
1915
|
+
import fs10 from "fs";
|
|
1916
|
+
import path15 from "path";
|
|
1551
1917
|
function registerDubCommand(parent) {
|
|
1552
1918
|
parent.command("dub").description("Dub a video/audio file into target languages (async)").option("--input <path>", "Source media file (use --input or --source-url)").option("--source-url <url>", "Public URL of source media").requiredOption("--target-lang <code>", "Target language ISO code (e.g. es, fr, de)").option("--source-lang <code>", "Source language (auto if omitted)").option("--num-speakers <n>", "Speaker count (auto if omitted)").option("--watermark", "Add ElevenLabs watermark", false).option("--start-time <s>", "Start time in seconds").option("--end-time <s>", "End time in seconds").option("--highest-resolution", "Use highest resolution video", false).option("--name <n>", "Project display name").option("--async", "Return job id immediately, do not wait", false).option("--wait", "Wait until status=dubbed (implied by --download)", false).option("--download", "Download dubbed media when ready", false).option("--poll-interval <s>", "Poll interval seconds", "10").option("--wait-timeout <ms>", "Poll timeout", "1800000").option("--output <path>", "Output path for downloaded dub").option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
1553
1919
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
@@ -1590,7 +1956,7 @@ function registerDubCommand(parent) {
|
|
|
1590
1956
|
);
|
|
1591
1957
|
logger.success(`Dub complete: ${start.dubbing_id}`);
|
|
1592
1958
|
if (opts.download) {
|
|
1593
|
-
const dest = opts.output ?
|
|
1959
|
+
const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
|
|
1594
1960
|
const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
|
|
1595
1961
|
await downloadDub(url, apiKey, dest);
|
|
1596
1962
|
logger.success(`Saved: ${dest}`);
|
|
@@ -1605,7 +1971,7 @@ Dubbed file: ${dest}`);
|
|
|
1605
1971
|
const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
|
|
1606
1972
|
console.log(JSON.stringify(status, null, 2));
|
|
1607
1973
|
if (opts.download) {
|
|
1608
|
-
const dest = opts.output ?
|
|
1974
|
+
const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
|
|
1609
1975
|
const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
|
|
1610
1976
|
await downloadDub(url, apiKey, dest);
|
|
1611
1977
|
console.log(`Saved: ${dest}`);
|
|
@@ -1613,7 +1979,7 @@ Dubbed file: ${dest}`);
|
|
|
1613
1979
|
});
|
|
1614
1980
|
}
|
|
1615
1981
|
async function downloadDub(url, apiKey, dest) {
|
|
1616
|
-
|
|
1982
|
+
fs10.mkdirSync(path15.dirname(dest), { recursive: true });
|
|
1617
1983
|
const res = await globalThis.fetch(url, {
|
|
1618
1984
|
headers: { "xi-api-key": apiKey },
|
|
1619
1985
|
signal: AbortSignal.timeout(6e5)
|
|
@@ -1622,7 +1988,7 @@ async function downloadDub(url, apiKey, dest) {
|
|
|
1622
1988
|
throw new Error(`Failed to download dub: HTTP ${res.status}`);
|
|
1623
1989
|
}
|
|
1624
1990
|
const arr = new Uint8Array(await res.arrayBuffer());
|
|
1625
|
-
|
|
1991
|
+
fs10.writeFileSync(dest, arr);
|
|
1626
1992
|
void downloadFile;
|
|
1627
1993
|
}
|
|
1628
1994
|
|
|
@@ -1637,7 +2003,7 @@ function registerIsolateCommand(parent) {
|
|
|
1637
2003
|
const bytes = await apiPostBinary("audio-isolation", form, apiKey, {
|
|
1638
2004
|
timeoutMs: 6e5
|
|
1639
2005
|
});
|
|
1640
|
-
const dest =
|
|
2006
|
+
const dest = saveBytes2({
|
|
1641
2007
|
bytes,
|
|
1642
2008
|
task: "isolate",
|
|
1643
2009
|
ext: "mp3",
|
|
@@ -1651,8 +2017,8 @@ Isolated audio: ${dest}`);
|
|
|
1651
2017
|
|
|
1652
2018
|
// src/providers/elevenlabs/commands/music.ts
|
|
1653
2019
|
init_errors();
|
|
1654
|
-
import
|
|
1655
|
-
import
|
|
2020
|
+
import fs11 from "fs";
|
|
2021
|
+
import path16 from "path";
|
|
1656
2022
|
function registerMusicCommand(parent) {
|
|
1657
2023
|
parent.command("music").description("Generate music from a text prompt or composition plan").option("--prompt <str>", "Music prompt (mutually exclusive with --plan)").option("--plan <path>", "Path to composition plan JSON file").option("--music-length-ms <n>", "Total length in milliseconds (default 30000)").option("--model <id>", "Music model", "music_v1").option(
|
|
1658
2024
|
"--format <fmt>",
|
|
@@ -1667,9 +2033,9 @@ function registerMusicCommand(parent) {
|
|
|
1667
2033
|
const payload = { model_id: opts.model };
|
|
1668
2034
|
if (opts.prompt) payload.prompt = opts.prompt;
|
|
1669
2035
|
if (opts.plan) {
|
|
1670
|
-
const planPath =
|
|
1671
|
-
if (!
|
|
1672
|
-
const planJson = JSON.parse(
|
|
2036
|
+
const planPath = path16.resolve(opts.plan);
|
|
2037
|
+
if (!fs11.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
|
|
2038
|
+
const planJson = JSON.parse(fs11.readFileSync(planPath, "utf8"));
|
|
1673
2039
|
payload.composition_plan = planJson;
|
|
1674
2040
|
}
|
|
1675
2041
|
if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
|
|
@@ -1678,7 +2044,7 @@ function registerMusicCommand(parent) {
|
|
|
1678
2044
|
query: { output_format: opts.format },
|
|
1679
2045
|
timeoutMs: 6e5
|
|
1680
2046
|
});
|
|
1681
|
-
const dest =
|
|
2047
|
+
const dest = saveBytes2({
|
|
1682
2048
|
bytes,
|
|
1683
2049
|
task: "music",
|
|
1684
2050
|
ext: extFromFormat(opts.format),
|
|
@@ -1715,7 +2081,7 @@ function registerSfxCommand(parent) {
|
|
|
1715
2081
|
query: { output_format: opts.format },
|
|
1716
2082
|
timeoutMs: 3e5
|
|
1717
2083
|
});
|
|
1718
|
-
const dest =
|
|
2084
|
+
const dest = saveBytes2({
|
|
1719
2085
|
bytes,
|
|
1720
2086
|
task: "sfx",
|
|
1721
2087
|
ext: extFromFormat(opts.format),
|
|
@@ -1728,8 +2094,8 @@ Generated SFX: ${dest}`);
|
|
|
1728
2094
|
}
|
|
1729
2095
|
|
|
1730
2096
|
// src/providers/elevenlabs/commands/transcribe.ts
|
|
1731
|
-
import
|
|
1732
|
-
import
|
|
2097
|
+
import fs12 from "fs";
|
|
2098
|
+
import path17 from "path";
|
|
1733
2099
|
function registerTranscribeCommand(parent) {
|
|
1734
2100
|
parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
|
|
1735
2101
|
"--model <id>",
|
|
@@ -1752,7 +2118,7 @@ function registerTranscribeCommand(parent) {
|
|
|
1752
2118
|
});
|
|
1753
2119
|
const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
|
|
1754
2120
|
const dir = getOutputDir();
|
|
1755
|
-
const dest = opts.output ?
|
|
2121
|
+
const dest = opts.output ? path17.resolve(opts.output) : path17.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
|
|
1756
2122
|
let body;
|
|
1757
2123
|
switch (opts.format) {
|
|
1758
2124
|
case "json":
|
|
@@ -1767,8 +2133,8 @@ function registerTranscribeCommand(parent) {
|
|
|
1767
2133
|
default:
|
|
1768
2134
|
body = data.text ?? "";
|
|
1769
2135
|
}
|
|
1770
|
-
|
|
1771
|
-
|
|
2136
|
+
fs12.mkdirSync(path17.dirname(dest), { recursive: true });
|
|
2137
|
+
fs12.writeFileSync(dest, body);
|
|
1772
2138
|
logger.success(`Saved transcript: ${dest}`);
|
|
1773
2139
|
if (data.language_code) {
|
|
1774
2140
|
logger.info(
|
|
@@ -1846,7 +2212,7 @@ function registerTtsCommand(parent) {
|
|
|
1846
2212
|
const bytes = await apiPostBinary(`text-to-speech/${opts.voice}`, payload, apiKey, {
|
|
1847
2213
|
query: { output_format: opts.format }
|
|
1848
2214
|
});
|
|
1849
|
-
const dest =
|
|
2215
|
+
const dest = saveBytes2({
|
|
1850
2216
|
bytes,
|
|
1851
2217
|
task: "tts",
|
|
1852
2218
|
ext: extFromFormat(opts.format),
|
|
@@ -1888,7 +2254,7 @@ function registerVoiceChangerCommand(parent) {
|
|
|
1888
2254
|
if (!bytes || bytes.length === 0) {
|
|
1889
2255
|
throw new ValidationError("Empty response from voice-changer");
|
|
1890
2256
|
}
|
|
1891
|
-
const dest =
|
|
2257
|
+
const dest = saveBytes2({
|
|
1892
2258
|
bytes,
|
|
1893
2259
|
task: "voice_changer",
|
|
1894
2260
|
ext: extFromFormat(opts.format),
|
|
@@ -1959,7 +2325,7 @@ ${data.voices.length} voices${data.has_more ? " (more available)" : ""}`);
|
|
|
1959
2325
|
logger.success(`${data.previews.length} previews generated`);
|
|
1960
2326
|
data.previews.forEach((p, i) => {
|
|
1961
2327
|
const bytes = Buffer.from(p.audio_base_64, "base64");
|
|
1962
|
-
const dest =
|
|
2328
|
+
const dest = saveBytes2({
|
|
1963
2329
|
bytes,
|
|
1964
2330
|
task: `${opts.outputPrefix}_${i + 1}_${p.generated_voice_id}`,
|
|
1965
2331
|
ext: extFromFormat(TASK_DEFAULTS.ttsFormat),
|
|
@@ -2015,16 +2381,16 @@ function registerElevenLabsCommands(program2) {
|
|
|
2015
2381
|
|
|
2016
2382
|
// src/providers/gemini/commands/analyze.ts
|
|
2017
2383
|
init_errors();
|
|
2018
|
-
import
|
|
2384
|
+
import fs15 from "fs";
|
|
2019
2385
|
|
|
2020
2386
|
// src/providers/gemini/client.ts
|
|
2021
|
-
import
|
|
2022
|
-
import
|
|
2387
|
+
import fs13 from "fs";
|
|
2388
|
+
import path19 from "path";
|
|
2023
2389
|
init_errors();
|
|
2024
2390
|
init_http_client();
|
|
2025
2391
|
|
|
2026
2392
|
// src/providers/gemini/task-resolver.ts
|
|
2027
|
-
import
|
|
2393
|
+
import path18 from "path";
|
|
2028
2394
|
var MIME_MAP = {
|
|
2029
2395
|
// Audio
|
|
2030
2396
|
".mp3": "audio/mp3",
|
|
@@ -2065,7 +2431,7 @@ var MIME_MAP = {
|
|
|
2065
2431
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
2066
2432
|
};
|
|
2067
2433
|
function getMimeType(filePath) {
|
|
2068
|
-
const ext =
|
|
2434
|
+
const ext = path18.extname(filePath).toLowerCase();
|
|
2069
2435
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
2070
2436
|
}
|
|
2071
2437
|
function requiresProcessingWait(mimeType) {
|
|
@@ -2091,8 +2457,8 @@ async function uploadFile(filePath, opts = {}) {
|
|
|
2091
2457
|
const apiKey = requireApiKey();
|
|
2092
2458
|
const { timeoutMs = 3e5, logger } = opts;
|
|
2093
2459
|
const mimeType = getMimeType(filePath);
|
|
2094
|
-
const fileBytes =
|
|
2095
|
-
const displayName =
|
|
2460
|
+
const fileBytes = fs13.readFileSync(filePath);
|
|
2461
|
+
const displayName = path19.basename(filePath);
|
|
2096
2462
|
const boundary = `multix_${Date.now()}`;
|
|
2097
2463
|
const metaJson = JSON.stringify({ file: { displayName, mimeType } });
|
|
2098
2464
|
const body = Buffer.concat([
|
|
@@ -2181,12 +2547,12 @@ function extractImages(resp) {
|
|
|
2181
2547
|
).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
|
|
2182
2548
|
}
|
|
2183
2549
|
function inlinePart(filePath) {
|
|
2184
|
-
const data =
|
|
2550
|
+
const data = fs13.readFileSync(filePath).toString("base64");
|
|
2185
2551
|
const mimeType = getMimeType(filePath);
|
|
2186
2552
|
return { inlineData: { mimeType, data } };
|
|
2187
2553
|
}
|
|
2188
2554
|
function shouldUseFileApi(filePath) {
|
|
2189
|
-
const stat =
|
|
2555
|
+
const stat = fs13.statSync(filePath);
|
|
2190
2556
|
return stat.size > 20 * 1024 * 1024;
|
|
2191
2557
|
}
|
|
2192
2558
|
function sleep(ms) {
|
|
@@ -2239,13 +2605,13 @@ function getDefaultModel(task) {
|
|
|
2239
2605
|
}
|
|
2240
2606
|
|
|
2241
2607
|
// src/providers/gemini/result-formatter.ts
|
|
2242
|
-
import
|
|
2243
|
-
import
|
|
2608
|
+
import fs14 from "fs";
|
|
2609
|
+
import path20 from "path";
|
|
2244
2610
|
function formatResults(results, format, outputPath) {
|
|
2245
2611
|
const content = renderFormat(results, format);
|
|
2246
2612
|
if (outputPath) {
|
|
2247
|
-
|
|
2248
|
-
|
|
2613
|
+
fs14.mkdirSync(path20.dirname(path20.resolve(outputPath)), { recursive: true });
|
|
2614
|
+
fs14.writeFileSync(outputPath, content, "utf8");
|
|
2249
2615
|
} else {
|
|
2250
2616
|
process.stdout.write(content);
|
|
2251
2617
|
}
|
|
@@ -2312,7 +2678,7 @@ function registerAnalyzeCommand(parent) {
|
|
|
2312
2678
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
2313
2679
|
const model = opts.model ?? getDefaultModel("analyze");
|
|
2314
2680
|
for (const f of opts.files) {
|
|
2315
|
-
if (!
|
|
2681
|
+
if (!fs15.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
2316
2682
|
}
|
|
2317
2683
|
const results = [];
|
|
2318
2684
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -2358,14 +2724,14 @@ function registerAnalyzeCommand(parent) {
|
|
|
2358
2724
|
|
|
2359
2725
|
// src/providers/gemini/commands/extract.ts
|
|
2360
2726
|
init_errors();
|
|
2361
|
-
import
|
|
2727
|
+
import fs16 from "fs";
|
|
2362
2728
|
function registerExtractCommand(parent) {
|
|
2363
2729
|
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(
|
|
2364
2730
|
async (opts) => {
|
|
2365
2731
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
2366
2732
|
const model = opts.model ?? getDefaultModel("extract");
|
|
2367
2733
|
for (const f of opts.files) {
|
|
2368
|
-
if (!
|
|
2734
|
+
if (!fs16.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
2369
2735
|
}
|
|
2370
2736
|
const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
|
|
2371
2737
|
const results = [];
|
|
@@ -2407,8 +2773,8 @@ function registerExtractCommand(parent) {
|
|
|
2407
2773
|
init_errors();
|
|
2408
2774
|
|
|
2409
2775
|
// src/providers/gemini/generators/speech.ts
|
|
2410
|
-
import
|
|
2411
|
-
import
|
|
2776
|
+
import fs17 from "fs";
|
|
2777
|
+
import path21 from "path";
|
|
2412
2778
|
|
|
2413
2779
|
// src/providers/gemini/voices.ts
|
|
2414
2780
|
var GEMINI_TTS_VOICES = [
|
|
@@ -2491,12 +2857,12 @@ async function generateGeminiSpeech(opts) {
|
|
|
2491
2857
|
const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
|
|
2492
2858
|
const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
|
|
2493
2859
|
const outDir = getOutputDir();
|
|
2494
|
-
const dest =
|
|
2495
|
-
|
|
2860
|
+
const dest = path21.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
2861
|
+
fs17.writeFileSync(dest, outBytes);
|
|
2496
2862
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
2497
2863
|
if (output) {
|
|
2498
|
-
|
|
2499
|
-
|
|
2864
|
+
fs17.mkdirSync(path21.dirname(path21.resolve(output)), { recursive: true });
|
|
2865
|
+
fs17.copyFileSync(dest, output);
|
|
2500
2866
|
logger?.success(`Copied to: ${output}`);
|
|
2501
2867
|
}
|
|
2502
2868
|
return { status: "success", generatedAudio: dest, model, mimeType: audio.mimeType };
|
|
@@ -2595,8 +2961,8 @@ function collectSpeakers(value, prev) {
|
|
|
2595
2961
|
}
|
|
2596
2962
|
|
|
2597
2963
|
// src/providers/gemini/commands/generate-video.ts
|
|
2598
|
-
import
|
|
2599
|
-
import
|
|
2964
|
+
import fs18 from "fs";
|
|
2965
|
+
import path22 from "path";
|
|
2600
2966
|
init_errors();
|
|
2601
2967
|
init_http_client();
|
|
2602
2968
|
var BASE2 = "https://generativelanguage.googleapis.com";
|
|
@@ -2630,10 +2996,10 @@ function registerGenerateVideoCommand(parent) {
|
|
|
2630
2996
|
const contentParts = [{ text: opts.prompt }];
|
|
2631
2997
|
if (opts.referenceImages?.length) {
|
|
2632
2998
|
for (const imgPath of opts.referenceImages.slice(0, 2)) {
|
|
2633
|
-
if (!
|
|
2999
|
+
if (!fs18.existsSync(imgPath))
|
|
2634
3000
|
throw new ValidationError(`Reference image not found: ${imgPath}`);
|
|
2635
|
-
const data =
|
|
2636
|
-
const ext =
|
|
3001
|
+
const data = fs18.readFileSync(imgPath).toString("base64");
|
|
3002
|
+
const ext = path22.extname(imgPath).toLowerCase();
|
|
2637
3003
|
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
2638
3004
|
contentParts.push({ inlineData: { mimeType, data } });
|
|
2639
3005
|
}
|
|
@@ -2682,13 +3048,13 @@ function registerGenerateVideoCommand(parent) {
|
|
|
2682
3048
|
);
|
|
2683
3049
|
}
|
|
2684
3050
|
const outDir = getOutputDir();
|
|
2685
|
-
const dest =
|
|
3051
|
+
const dest = path22.join(outDir, `veo_generated_${Date.now()}.mp4`);
|
|
2686
3052
|
const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
|
|
2687
3053
|
await downloadFile2(videoUri, dest);
|
|
2688
3054
|
logger.success(`Saved video: ${dest}`);
|
|
2689
3055
|
if (opts.output) {
|
|
2690
|
-
|
|
2691
|
-
|
|
3056
|
+
fs18.mkdirSync(path22.dirname(path22.resolve(opts.output)), { recursive: true });
|
|
3057
|
+
fs18.copyFileSync(dest, opts.output);
|
|
2692
3058
|
logger.success(`Copied to: ${opts.output}`);
|
|
2693
3059
|
}
|
|
2694
3060
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -2707,8 +3073,8 @@ function sleep2(ms) {
|
|
|
2707
3073
|
|
|
2708
3074
|
// src/providers/gemini/commands/generate.ts
|
|
2709
3075
|
init_errors();
|
|
2710
|
-
import
|
|
2711
|
-
import
|
|
3076
|
+
import fs19 from "fs";
|
|
3077
|
+
import path23 from "path";
|
|
2712
3078
|
function registerGenerateCommand(parent) {
|
|
2713
3079
|
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").option("-v, --verbose", "Verbose logging").action(
|
|
2714
3080
|
async (opts) => {
|
|
@@ -2750,8 +3116,8 @@ function registerGenerateCommand(parent) {
|
|
|
2750
3116
|
const outDir = getOutputDir();
|
|
2751
3117
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
2752
3118
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
2753
|
-
const dest =
|
|
2754
|
-
|
|
3119
|
+
const dest = path23.join(outDir, fname);
|
|
3120
|
+
fs19.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
2755
3121
|
savedFiles.push(dest);
|
|
2756
3122
|
logger.success(`Saved: ${dest}`);
|
|
2757
3123
|
}
|
|
@@ -2766,8 +3132,8 @@ function registerGenerateCommand(parent) {
|
|
|
2766
3132
|
process.exit(1);
|
|
2767
3133
|
}
|
|
2768
3134
|
if (opts.output && savedFiles[0]) {
|
|
2769
|
-
|
|
2770
|
-
|
|
3135
|
+
fs19.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
|
|
3136
|
+
fs19.copyFileSync(savedFiles[0], opts.output);
|
|
2771
3137
|
logger.success(`Copied to: ${opts.output}`);
|
|
2772
3138
|
}
|
|
2773
3139
|
console.log(`
|
|
@@ -2778,8 +3144,8 @@ Generated ${savedFiles.length} image(s):`);
|
|
|
2778
3144
|
}
|
|
2779
3145
|
|
|
2780
3146
|
// src/providers/gemini/commands/image-to-image.ts
|
|
2781
|
-
import
|
|
2782
|
-
import
|
|
3147
|
+
import fs20 from "fs";
|
|
3148
|
+
import path24 from "path";
|
|
2783
3149
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
2784
3150
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
2785
3151
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -2840,14 +3206,14 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
2840
3206
|
const img = images[i];
|
|
2841
3207
|
if (!img) continue;
|
|
2842
3208
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
2843
|
-
const dest =
|
|
2844
|
-
|
|
3209
|
+
const dest = path24.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
3210
|
+
fs20.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
2845
3211
|
saved.push(dest);
|
|
2846
3212
|
logger.success(`Saved: ${dest}`);
|
|
2847
3213
|
}
|
|
2848
3214
|
if (opts.output && saved[0]) {
|
|
2849
|
-
|
|
2850
|
-
|
|
3215
|
+
fs20.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
|
|
3216
|
+
fs20.copyFileSync(saved[0], opts.output);
|
|
2851
3217
|
logger.success(`Copied to: ${opts.output}`);
|
|
2852
3218
|
}
|
|
2853
3219
|
console.log(`
|
|
@@ -2874,7 +3240,7 @@ function sniffImageMime(buf, url) {
|
|
|
2874
3240
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
2875
3241
|
return "image/webp";
|
|
2876
3242
|
}
|
|
2877
|
-
const ext =
|
|
3243
|
+
const ext = path24.extname(new URL(url).pathname).toLowerCase();
|
|
2878
3244
|
if (ext === ".png") return "image/png";
|
|
2879
3245
|
if (ext === ".webp") return "image/webp";
|
|
2880
3246
|
if (ext === ".gif") return "image/gif";
|
|
@@ -2882,17 +3248,17 @@ function sniffImageMime(buf, url) {
|
|
|
2882
3248
|
}
|
|
2883
3249
|
|
|
2884
3250
|
// src/providers/gemini/commands/image-to-video.ts
|
|
2885
|
-
import
|
|
2886
|
-
import
|
|
3251
|
+
import fs21 from "fs";
|
|
3252
|
+
import path25 from "path";
|
|
2887
3253
|
init_errors();
|
|
2888
3254
|
init_http_client();
|
|
2889
3255
|
var BASE3 = "https://generativelanguage.googleapis.com";
|
|
2890
3256
|
function imagePart(imgPath) {
|
|
2891
|
-
if (!
|
|
3257
|
+
if (!fs21.existsSync(imgPath)) {
|
|
2892
3258
|
throw new ValidationError(`Image not found: ${imgPath}`);
|
|
2893
3259
|
}
|
|
2894
|
-
const data =
|
|
2895
|
-
const ext =
|
|
3260
|
+
const data = fs21.readFileSync(imgPath).toString("base64");
|
|
3261
|
+
const ext = path25.extname(imgPath).toLowerCase();
|
|
2896
3262
|
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
2897
3263
|
return { inlineData: { mimeType, data } };
|
|
2898
3264
|
}
|
|
@@ -2965,12 +3331,12 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
2965
3331
|
"gemini"
|
|
2966
3332
|
);
|
|
2967
3333
|
}
|
|
2968
|
-
const dest =
|
|
3334
|
+
const dest = path25.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
2969
3335
|
await downloadFile(videoUri, dest);
|
|
2970
3336
|
logger.success(`Saved video: ${dest}`);
|
|
2971
3337
|
if (opts.output) {
|
|
2972
|
-
|
|
2973
|
-
|
|
3338
|
+
fs21.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
|
|
3339
|
+
fs21.copyFileSync(dest, opts.output);
|
|
2974
3340
|
logger.success(`Copied to: ${opts.output}`);
|
|
2975
3341
|
}
|
|
2976
3342
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -2986,14 +3352,14 @@ Generated video: ${dest}`);
|
|
|
2986
3352
|
|
|
2987
3353
|
// src/providers/gemini/commands/transcribe.ts
|
|
2988
3354
|
init_errors();
|
|
2989
|
-
import
|
|
3355
|
+
import fs22 from "fs";
|
|
2990
3356
|
function registerTranscribeCommand2(parent) {
|
|
2991
3357
|
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(
|
|
2992
3358
|
async (opts) => {
|
|
2993
3359
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
2994
3360
|
const model = opts.model ?? getDefaultModel("transcribe");
|
|
2995
3361
|
for (const f of opts.files) {
|
|
2996
|
-
if (!
|
|
3362
|
+
if (!fs22.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
2997
3363
|
}
|
|
2998
3364
|
const results = [];
|
|
2999
3365
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -3051,8 +3417,8 @@ function registerGeminiCommands(program2) {
|
|
|
3051
3417
|
|
|
3052
3418
|
// src/providers/leonardo/commands/generate.ts
|
|
3053
3419
|
init_http_client();
|
|
3054
|
-
import
|
|
3055
|
-
import
|
|
3420
|
+
import fs23 from "fs";
|
|
3421
|
+
import path26 from "path";
|
|
3056
3422
|
|
|
3057
3423
|
// src/providers/leonardo/client.ts
|
|
3058
3424
|
init_errors();
|
|
@@ -3126,11 +3492,11 @@ var LeonardoClient = class {
|
|
|
3126
3492
|
}
|
|
3127
3493
|
apiKey;
|
|
3128
3494
|
baseUrl;
|
|
3129
|
-
async request(
|
|
3495
|
+
async request(path46, opts = {}) {
|
|
3130
3496
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
3131
|
-
const v2Override =
|
|
3497
|
+
const v2Override = path46.startsWith("v2:");
|
|
3132
3498
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
3133
|
-
const finalPath = v2Override ?
|
|
3499
|
+
const finalPath = v2Override ? path46.slice(3) : path46;
|
|
3134
3500
|
const url = new URL(finalBase + finalPath);
|
|
3135
3501
|
if (opts.query) {
|
|
3136
3502
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -3149,14 +3515,14 @@ var LeonardoClient = class {
|
|
|
3149
3515
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
3150
3516
|
});
|
|
3151
3517
|
}
|
|
3152
|
-
get(
|
|
3153
|
-
return this.request(
|
|
3518
|
+
get(path46, query, logger) {
|
|
3519
|
+
return this.request(path46, { method: "GET", query, logger });
|
|
3154
3520
|
}
|
|
3155
|
-
post(
|
|
3156
|
-
return this.request(
|
|
3521
|
+
post(path46, body, logger) {
|
|
3522
|
+
return this.request(path46, { method: "POST", body, logger });
|
|
3157
3523
|
}
|
|
3158
|
-
delete(
|
|
3159
|
-
return this.request(
|
|
3524
|
+
delete(path46, logger) {
|
|
3525
|
+
return this.request(path46, { method: "DELETE", logger });
|
|
3160
3526
|
}
|
|
3161
3527
|
};
|
|
3162
3528
|
function createLeonardoClient() {
|
|
@@ -3167,7 +3533,7 @@ function createLeonardoClient() {
|
|
|
3167
3533
|
function extFromUrl(url, fallback = ".png") {
|
|
3168
3534
|
try {
|
|
3169
3535
|
const u = new URL(url);
|
|
3170
|
-
const e =
|
|
3536
|
+
const e = path26.extname(u.pathname);
|
|
3171
3537
|
return e.length > 0 ? e : fallback;
|
|
3172
3538
|
} catch {
|
|
3173
3539
|
return fallback;
|
|
@@ -3252,7 +3618,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
3252
3618
|
if (!img) continue;
|
|
3253
3619
|
const ext = extFromUrl(img.url);
|
|
3254
3620
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
3255
|
-
const filepath =
|
|
3621
|
+
const filepath = path26.join(outDir, filename);
|
|
3256
3622
|
try {
|
|
3257
3623
|
await downloadFile(img.url, filepath);
|
|
3258
3624
|
saved.push(filepath);
|
|
@@ -3262,9 +3628,9 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
3262
3628
|
}
|
|
3263
3629
|
}
|
|
3264
3630
|
if (opts.output && saved[0]) {
|
|
3265
|
-
const dest =
|
|
3266
|
-
|
|
3267
|
-
|
|
3631
|
+
const dest = path26.resolve(opts.output);
|
|
3632
|
+
fs23.mkdirSync(path26.dirname(dest), { recursive: true });
|
|
3633
|
+
fs23.copyFileSync(saved[0], dest);
|
|
3268
3634
|
logger.success(`Copied to ${dest}`);
|
|
3269
3635
|
}
|
|
3270
3636
|
console.log(`
|
|
@@ -3276,11 +3642,11 @@ Generated ${saved.length} image(s):`);
|
|
|
3276
3642
|
|
|
3277
3643
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
3278
3644
|
init_http_client();
|
|
3279
|
-
import
|
|
3280
|
-
import
|
|
3645
|
+
import fs24 from "fs";
|
|
3646
|
+
import path27 from "path";
|
|
3281
3647
|
function extFromUrl2(url, fallback = ".png") {
|
|
3282
3648
|
try {
|
|
3283
|
-
return
|
|
3649
|
+
return path27.extname(new URL(url).pathname) || fallback;
|
|
3284
3650
|
} catch {
|
|
3285
3651
|
return fallback;
|
|
3286
3652
|
}
|
|
@@ -3342,7 +3708,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
3342
3708
|
if (!img) continue;
|
|
3343
3709
|
const ext = extFromUrl2(img.url);
|
|
3344
3710
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
3345
|
-
const filepath =
|
|
3711
|
+
const filepath = path27.join(outDir, filename);
|
|
3346
3712
|
try {
|
|
3347
3713
|
await downloadFile(img.url, filepath);
|
|
3348
3714
|
saved.push(filepath);
|
|
@@ -3352,9 +3718,9 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
3352
3718
|
}
|
|
3353
3719
|
}
|
|
3354
3720
|
if (opts.output && saved[0]) {
|
|
3355
|
-
const dest =
|
|
3356
|
-
|
|
3357
|
-
|
|
3721
|
+
const dest = path27.resolve(opts.output);
|
|
3722
|
+
fs24.mkdirSync(path27.dirname(dest), { recursive: true });
|
|
3723
|
+
fs24.copyFileSync(saved[0], dest);
|
|
3358
3724
|
logger.success(`Copied to ${dest}`);
|
|
3359
3725
|
}
|
|
3360
3726
|
console.log(`
|
|
@@ -3366,7 +3732,7 @@ Generated ${saved.length} image(s):`);
|
|
|
3366
3732
|
|
|
3367
3733
|
// src/providers/leonardo/commands/video.ts
|
|
3368
3734
|
init_http_client();
|
|
3369
|
-
import
|
|
3735
|
+
import path28 from "path";
|
|
3370
3736
|
function v2DimsFor(resolution) {
|
|
3371
3737
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
3372
3738
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -3466,19 +3832,19 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
3466
3832
|
logger.error("No generation in response");
|
|
3467
3833
|
process.exit(1);
|
|
3468
3834
|
}
|
|
3469
|
-
const
|
|
3835
|
+
const videoUrl2 = gen.generated_images?.find(
|
|
3470
3836
|
(img) => typeof img.url === "string" && /\.(mp4|webm|mov)(\?|$)/i.test(img.url)
|
|
3471
3837
|
)?.url ?? gen.generated_images?.[0]?.motionMP4URL;
|
|
3472
3838
|
if (!opts.download) {
|
|
3473
|
-
if (typeof
|
|
3839
|
+
if (typeof videoUrl2 === "string") console.log(videoUrl2);
|
|
3474
3840
|
return;
|
|
3475
3841
|
}
|
|
3476
|
-
if (typeof
|
|
3842
|
+
if (typeof videoUrl2 !== "string") {
|
|
3477
3843
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
3478
3844
|
process.exit(1);
|
|
3479
3845
|
}
|
|
3480
|
-
const outPath = opts.output ??
|
|
3481
|
-
await downloadFile(
|
|
3846
|
+
const outPath = opts.output ?? path28.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
3847
|
+
await downloadFile(videoUrl2, outPath);
|
|
3482
3848
|
logger.success(`Saved ${outPath}`);
|
|
3483
3849
|
await maybeDownloadThumb(gen, outPath, {
|
|
3484
3850
|
skip: !opts.thumb,
|
|
@@ -3584,7 +3950,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
3584
3950
|
|
|
3585
3951
|
// src/providers/leonardo/commands/status.ts
|
|
3586
3952
|
init_http_client();
|
|
3587
|
-
import
|
|
3953
|
+
import path29 from "path";
|
|
3588
3954
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
3589
3955
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
3590
3956
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -3649,7 +4015,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
3649
4015
|
const outDir = getOutputDir();
|
|
3650
4016
|
let idx = 0;
|
|
3651
4017
|
for (const url of videos) {
|
|
3652
|
-
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output :
|
|
4018
|
+
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path29.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
|
|
3653
4019
|
await downloadFile(url, dest);
|
|
3654
4020
|
logger.success(`Saved ${dest}`);
|
|
3655
4021
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -3662,7 +4028,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
3662
4028
|
for (const url of images) {
|
|
3663
4029
|
const m = url.match(IMAGE_EXT_RE2);
|
|
3664
4030
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
3665
|
-
const dest =
|
|
4031
|
+
const dest = path29.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
3666
4032
|
await downloadFile(url, dest);
|
|
3667
4033
|
logger.success(`Saved ${dest}`);
|
|
3668
4034
|
idx++;
|
|
@@ -3821,8 +4187,8 @@ function sleep4(ms) {
|
|
|
3821
4187
|
|
|
3822
4188
|
// src/providers/minimax/generators/music.ts
|
|
3823
4189
|
init_http_client();
|
|
3824
|
-
import
|
|
3825
|
-
import
|
|
4190
|
+
import fs25 from "fs";
|
|
4191
|
+
import path30 from "path";
|
|
3826
4192
|
async function generateMinimaxMusic(opts) {
|
|
3827
4193
|
const {
|
|
3828
4194
|
apiKey,
|
|
@@ -3867,14 +4233,14 @@ async function generateMinimaxMusic(opts) {
|
|
|
3867
4233
|
}
|
|
3868
4234
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
3869
4235
|
const outDir = getOutputDir();
|
|
3870
|
-
const dest =
|
|
4236
|
+
const dest = path30.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
3871
4237
|
try {
|
|
3872
4238
|
if (audioData.startsWith("http")) {
|
|
3873
4239
|
const bytes = await fetchBytes(audioData);
|
|
3874
|
-
|
|
4240
|
+
fs25.writeFileSync(dest, bytes);
|
|
3875
4241
|
} else {
|
|
3876
4242
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
3877
|
-
|
|
4243
|
+
fs25.writeFileSync(dest, audioBytes);
|
|
3878
4244
|
}
|
|
3879
4245
|
} catch (e) {
|
|
3880
4246
|
return {
|
|
@@ -3885,8 +4251,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
3885
4251
|
const durSec = durationMs / 1e3;
|
|
3886
4252
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
3887
4253
|
if (output) {
|
|
3888
|
-
|
|
3889
|
-
|
|
4254
|
+
fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
|
|
4255
|
+
fs25.copyFileSync(dest, output);
|
|
3890
4256
|
}
|
|
3891
4257
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
3892
4258
|
}
|
|
@@ -3960,8 +4326,8 @@ Generated music: ${result.generatedAudio}`);
|
|
|
3960
4326
|
init_errors();
|
|
3961
4327
|
|
|
3962
4328
|
// src/providers/minimax/generators/speech.ts
|
|
3963
|
-
import
|
|
3964
|
-
import
|
|
4329
|
+
import fs26 from "fs";
|
|
4330
|
+
import path31 from "path";
|
|
3965
4331
|
async function generateMinimaxSpeech(opts) {
|
|
3966
4332
|
const {
|
|
3967
4333
|
apiKey,
|
|
@@ -4008,12 +4374,12 @@ async function generateMinimaxSpeech(opts) {
|
|
|
4008
4374
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
4009
4375
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
4010
4376
|
const outDir = getOutputDir();
|
|
4011
|
-
const dest =
|
|
4012
|
-
|
|
4377
|
+
const dest = path31.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
4378
|
+
fs26.writeFileSync(dest, audioBytes);
|
|
4013
4379
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
4014
4380
|
if (output) {
|
|
4015
|
-
|
|
4016
|
-
|
|
4381
|
+
fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
|
|
4382
|
+
fs26.copyFileSync(dest, output);
|
|
4017
4383
|
}
|
|
4018
4384
|
return { status: "success", generatedAudio: dest, model };
|
|
4019
4385
|
}
|
|
@@ -4060,8 +4426,8 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
4060
4426
|
init_errors();
|
|
4061
4427
|
|
|
4062
4428
|
// src/providers/minimax/generators/video.ts
|
|
4063
|
-
import
|
|
4064
|
-
import
|
|
4429
|
+
import fs27 from "fs";
|
|
4430
|
+
import path32 from "path";
|
|
4065
4431
|
async function generateMinimaxVideo(opts) {
|
|
4066
4432
|
const {
|
|
4067
4433
|
apiKey,
|
|
@@ -4107,18 +4473,18 @@ async function generateMinimaxVideo(opts) {
|
|
|
4107
4473
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
4108
4474
|
}
|
|
4109
4475
|
const outDir = getOutputDir();
|
|
4110
|
-
const dest =
|
|
4476
|
+
const dest = path32.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
4111
4477
|
try {
|
|
4112
4478
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
4113
4479
|
} catch (e) {
|
|
4114
4480
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
4115
4481
|
}
|
|
4116
4482
|
const elapsed = (Date.now() - start) / 1e3;
|
|
4117
|
-
const stat =
|
|
4483
|
+
const stat = fs27.statSync(dest);
|
|
4118
4484
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
4119
4485
|
if (output) {
|
|
4120
|
-
|
|
4121
|
-
|
|
4486
|
+
fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
|
|
4487
|
+
fs27.copyFileSync(dest, output);
|
|
4122
4488
|
}
|
|
4123
4489
|
await maybeDownloadThumb(pollResult, dest, {
|
|
4124
4490
|
skip: thumb === false,
|
|
@@ -4181,8 +4547,8 @@ Generated video: ${result.generatedVideo}`);
|
|
|
4181
4547
|
// src/providers/minimax/generators/image.ts
|
|
4182
4548
|
init_errors();
|
|
4183
4549
|
init_http_client();
|
|
4184
|
-
import
|
|
4185
|
-
import
|
|
4550
|
+
import fs28 from "fs";
|
|
4551
|
+
import path33 from "path";
|
|
4186
4552
|
async function generateMinimaxImage(opts) {
|
|
4187
4553
|
const {
|
|
4188
4554
|
apiKey,
|
|
@@ -4220,8 +4586,8 @@ async function generateMinimaxImage(opts) {
|
|
|
4220
4586
|
try {
|
|
4221
4587
|
const bytes = await fetchBytes(url);
|
|
4222
4588
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
4223
|
-
const dest =
|
|
4224
|
-
|
|
4589
|
+
const dest = path33.join(outDir, fname);
|
|
4590
|
+
fs28.writeFileSync(dest, bytes);
|
|
4225
4591
|
savedFiles.push(dest);
|
|
4226
4592
|
logger?.success(`Saved: ${dest}`);
|
|
4227
4593
|
} catch (e) {
|
|
@@ -4232,8 +4598,8 @@ async function generateMinimaxImage(opts) {
|
|
|
4232
4598
|
}
|
|
4233
4599
|
}
|
|
4234
4600
|
if (output && savedFiles[0]) {
|
|
4235
|
-
|
|
4236
|
-
|
|
4601
|
+
fs28.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
|
|
4602
|
+
fs28.copyFileSync(savedFiles[0], output);
|
|
4237
4603
|
}
|
|
4238
4604
|
return { status: "success", generatedImages: savedFiles, model };
|
|
4239
4605
|
}
|
|
@@ -4269,8 +4635,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
|
|
|
4269
4635
|
// src/providers/minimax/commands/image-to-image.ts
|
|
4270
4636
|
init_errors();
|
|
4271
4637
|
init_http_client();
|
|
4272
|
-
import
|
|
4273
|
-
import
|
|
4638
|
+
import fs29 from "fs";
|
|
4639
|
+
import path34 from "path";
|
|
4274
4640
|
function registerMinimaxImageToImageCommand(parent) {
|
|
4275
4641
|
parent.command("image-to-image").alias("i2i").description(
|
|
4276
4642
|
"MiniMax character/subject preservation. CAVEAT: NOT free-form image editing \u2014 the --ref image is used only as a character/face reference for a NEW scene described by --prompt."
|
|
@@ -4325,8 +4691,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
4325
4691
|
if (!url) continue;
|
|
4326
4692
|
try {
|
|
4327
4693
|
const bytes = await fetchBytes(url);
|
|
4328
|
-
const dest =
|
|
4329
|
-
|
|
4694
|
+
const dest = path34.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
4695
|
+
fs29.writeFileSync(dest, bytes);
|
|
4330
4696
|
saved.push(dest);
|
|
4331
4697
|
logger.success(`Saved: ${dest}`);
|
|
4332
4698
|
} catch (e) {
|
|
@@ -4337,8 +4703,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
4337
4703
|
}
|
|
4338
4704
|
}
|
|
4339
4705
|
if (opts.output && saved[0]) {
|
|
4340
|
-
|
|
4341
|
-
|
|
4706
|
+
fs29.mkdirSync(path34.dirname(path34.resolve(opts.output)), { recursive: true });
|
|
4707
|
+
fs29.copyFileSync(saved[0], opts.output);
|
|
4342
4708
|
logger.success(`Copied to: ${opts.output}`);
|
|
4343
4709
|
}
|
|
4344
4710
|
console.log(`
|
|
@@ -4472,12 +4838,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
4472
4838
|
|
|
4473
4839
|
// src/providers/openai/openai-audio.ts
|
|
4474
4840
|
init_http_client();
|
|
4475
|
-
import
|
|
4476
|
-
import
|
|
4841
|
+
import fs31 from "fs";
|
|
4842
|
+
import path36 from "path";
|
|
4477
4843
|
|
|
4478
4844
|
// src/providers/openai/client.ts
|
|
4479
|
-
import
|
|
4480
|
-
import
|
|
4845
|
+
import fs30 from "fs";
|
|
4846
|
+
import path35 from "path";
|
|
4481
4847
|
init_errors();
|
|
4482
4848
|
init_http_client();
|
|
4483
4849
|
var DEFAULT_TIMEOUT_MS3 = 24e4;
|
|
@@ -4544,10 +4910,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
4544
4910
|
return Buffer.from(await res.arrayBuffer());
|
|
4545
4911
|
}
|
|
4546
4912
|
function fileFromPath(filePath) {
|
|
4547
|
-
const abs =
|
|
4548
|
-
if (!
|
|
4549
|
-
const bytes =
|
|
4550
|
-
return new File([new Uint8Array(bytes)],
|
|
4913
|
+
const abs = path35.resolve(filePath);
|
|
4914
|
+
if (!fs30.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
4915
|
+
const bytes = fs30.readFileSync(abs);
|
|
4916
|
+
return new File([new Uint8Array(bytes)], path35.basename(abs));
|
|
4551
4917
|
}
|
|
4552
4918
|
|
|
4553
4919
|
// src/providers/openai/openai-audio.ts
|
|
@@ -4586,7 +4952,7 @@ async function resolveAudioReferenceDataUrl(ref) {
|
|
|
4586
4952
|
}
|
|
4587
4953
|
function audioMimeFromReference(ref) {
|
|
4588
4954
|
const pathname = new URL(ref).pathname;
|
|
4589
|
-
const ext =
|
|
4955
|
+
const ext = path36.extname(pathname).toLowerCase();
|
|
4590
4956
|
switch (ext) {
|
|
4591
4957
|
case ".mp3":
|
|
4592
4958
|
return "audio/mpeg";
|
|
@@ -4605,12 +4971,12 @@ function audioMimeFromReference(ref) {
|
|
|
4605
4971
|
async function generateOpenAISpeech(opts) {
|
|
4606
4972
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
4607
4973
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
4608
|
-
const dest =
|
|
4609
|
-
|
|
4974
|
+
const dest = path36.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
4975
|
+
fs31.writeFileSync(dest, bytes);
|
|
4610
4976
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
4611
4977
|
if (opts.output) {
|
|
4612
|
-
|
|
4613
|
-
|
|
4978
|
+
fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
|
|
4979
|
+
fs31.copyFileSync(dest, opts.output);
|
|
4614
4980
|
}
|
|
4615
4981
|
return dest;
|
|
4616
4982
|
}
|
|
@@ -4632,9 +4998,9 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
4632
4998
|
);
|
|
4633
4999
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
4634
5000
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
4635
|
-
const dest = opts.output ?
|
|
4636
|
-
|
|
4637
|
-
|
|
5001
|
+
const dest = opts.output ? path36.resolve(opts.output) : path36.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
|
|
5002
|
+
fs31.mkdirSync(path36.dirname(dest), { recursive: true });
|
|
5003
|
+
fs31.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
|
|
4638
5004
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
4639
5005
|
return { path: dest, text };
|
|
4640
5006
|
}
|
|
@@ -4661,9 +5027,9 @@ Generated audio: ${dest}`);
|
|
|
4661
5027
|
}
|
|
4662
5028
|
|
|
4663
5029
|
// src/providers/openai/codex-image-driver.ts
|
|
4664
|
-
import
|
|
5030
|
+
import fs32 from "fs";
|
|
4665
5031
|
import os2 from "os";
|
|
4666
|
-
import
|
|
5032
|
+
import path37 from "path";
|
|
4667
5033
|
import { execa } from "execa";
|
|
4668
5034
|
init_errors();
|
|
4669
5035
|
var defaultRunner = async (cmd, args, opts) => {
|
|
@@ -4712,12 +5078,12 @@ function sanitizeCodexEnv() {
|
|
|
4712
5078
|
async function runCodexImageGeneration(opts) {
|
|
4713
5079
|
const runner = opts.runner ?? defaultRunner;
|
|
4714
5080
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
4715
|
-
const dest = opts.output ?
|
|
4716
|
-
|
|
4717
|
-
const tmp =
|
|
4718
|
-
const schema =
|
|
4719
|
-
const result =
|
|
4720
|
-
|
|
5081
|
+
const dest = opts.output ? path37.resolve(opts.output) : path37.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
|
|
5082
|
+
fs32.mkdirSync(path37.dirname(dest), { recursive: true });
|
|
5083
|
+
const tmp = fs32.mkdtempSync(path37.join(os2.tmpdir(), "multix-codex-image-"));
|
|
5084
|
+
const schema = path37.join(tmp, "schema.json");
|
|
5085
|
+
const result = path37.join(tmp, "result.json");
|
|
5086
|
+
fs32.writeFileSync(
|
|
4721
5087
|
schema,
|
|
4722
5088
|
JSON.stringify({
|
|
4723
5089
|
type: "object",
|
|
@@ -4747,7 +5113,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
4747
5113
|
if (res.exitCode !== 0) {
|
|
4748
5114
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
4749
5115
|
}
|
|
4750
|
-
if (!
|
|
5116
|
+
if (!fs32.existsSync(dest) || fs32.statSync(dest).size === 0) {
|
|
4751
5117
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
4752
5118
|
}
|
|
4753
5119
|
return { path: dest };
|
|
@@ -4765,8 +5131,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
4765
5131
|
// src/providers/openai/openai-image.ts
|
|
4766
5132
|
init_errors();
|
|
4767
5133
|
init_http_client();
|
|
4768
|
-
import
|
|
4769
|
-
import
|
|
5134
|
+
import fs33 from "fs";
|
|
5135
|
+
import path38 from "path";
|
|
4770
5136
|
function buildImageGenerationBody(opts) {
|
|
4771
5137
|
return {
|
|
4772
5138
|
model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
|
|
@@ -4799,14 +5165,14 @@ function saveOpenAIImages(opts) {
|
|
|
4799
5165
|
for (let i = 0; i < items.length; i++) {
|
|
4800
5166
|
const b64 = items[i]?.b64_json;
|
|
4801
5167
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
4802
|
-
const dest =
|
|
4803
|
-
|
|
5168
|
+
const dest = path38.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
5169
|
+
fs33.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
4804
5170
|
saved.push(dest);
|
|
4805
5171
|
opts.logger?.success(`Saved: ${dest}`);
|
|
4806
5172
|
}
|
|
4807
5173
|
if (opts.output && saved[0]) {
|
|
4808
|
-
|
|
4809
|
-
|
|
5174
|
+
fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
|
|
5175
|
+
fs33.copyFileSync(saved[0], opts.output);
|
|
4810
5176
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
4811
5177
|
}
|
|
4812
5178
|
return saved;
|
|
@@ -4815,7 +5181,7 @@ async function resolveImageRefBytes(ref) {
|
|
|
4815
5181
|
if (isHttpUrl(ref)) {
|
|
4816
5182
|
const bytes2 = await fetchBytes(ref);
|
|
4817
5183
|
const pathname = new URL(ref).pathname;
|
|
4818
|
-
const filename =
|
|
5184
|
+
const filename = path38.basename(pathname) || "reference.png";
|
|
4819
5185
|
return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
|
|
4820
5186
|
}
|
|
4821
5187
|
const file = fileFromPath(ref);
|
|
@@ -4860,7 +5226,7 @@ function sniffImageMime2(buf, name) {
|
|
|
4860
5226
|
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
4861
5227
|
if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
4862
5228
|
return "image/webp";
|
|
4863
|
-
const ext =
|
|
5229
|
+
const ext = path38.extname(name).toLowerCase();
|
|
4864
5230
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
4865
5231
|
if (ext === ".webp") return "image/webp";
|
|
4866
5232
|
return "image/png";
|
|
@@ -4988,8 +5354,8 @@ function registerOpenAICommands(program2) {
|
|
|
4988
5354
|
}
|
|
4989
5355
|
|
|
4990
5356
|
// src/providers/openrouter/client.ts
|
|
4991
|
-
import
|
|
4992
|
-
import
|
|
5357
|
+
import fs34 from "fs";
|
|
5358
|
+
import path39 from "path";
|
|
4993
5359
|
init_errors();
|
|
4994
5360
|
init_http_client();
|
|
4995
5361
|
|
|
@@ -5154,8 +5520,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
5154
5520
|
if (!url) continue;
|
|
5155
5521
|
try {
|
|
5156
5522
|
const bytes = await fetchBytes(url);
|
|
5157
|
-
const dest =
|
|
5158
|
-
|
|
5523
|
+
const dest = path39.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
5524
|
+
fs34.writeFileSync(dest, bytes);
|
|
5159
5525
|
savedFiles.push(dest);
|
|
5160
5526
|
logger?.success(`Saved: ${dest}`);
|
|
5161
5527
|
} catch (e) {
|
|
@@ -5166,8 +5532,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
5166
5532
|
}
|
|
5167
5533
|
}
|
|
5168
5534
|
if (output && savedFiles[0]) {
|
|
5169
|
-
|
|
5170
|
-
|
|
5535
|
+
fs34.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
|
|
5536
|
+
fs34.writeFileSync(output, fs34.readFileSync(savedFiles[0]));
|
|
5171
5537
|
}
|
|
5172
5538
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
5173
5539
|
}
|
|
@@ -5204,8 +5570,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
|
|
|
5204
5570
|
}
|
|
5205
5571
|
|
|
5206
5572
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
5207
|
-
import
|
|
5208
|
-
import
|
|
5573
|
+
import fs35 from "fs";
|
|
5574
|
+
import path40 from "path";
|
|
5209
5575
|
init_errors();
|
|
5210
5576
|
init_http_client();
|
|
5211
5577
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
@@ -5297,14 +5663,14 @@ async function runImageToImage(opts) {
|
|
|
5297
5663
|
const url = parsed.urls[i];
|
|
5298
5664
|
if (!url) continue;
|
|
5299
5665
|
const bytes = await fetchBytes(url);
|
|
5300
|
-
const dest =
|
|
5301
|
-
|
|
5666
|
+
const dest = path40.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
5667
|
+
fs35.writeFileSync(dest, bytes);
|
|
5302
5668
|
saved.push(dest);
|
|
5303
5669
|
opts.logger.success(`Saved: ${dest}`);
|
|
5304
5670
|
}
|
|
5305
5671
|
if (opts.output && saved[0]) {
|
|
5306
|
-
|
|
5307
|
-
|
|
5672
|
+
fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
|
|
5673
|
+
fs35.copyFileSync(saved[0], opts.output);
|
|
5308
5674
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
5309
5675
|
}
|
|
5310
5676
|
return saved;
|
|
@@ -5350,13 +5716,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
5350
5716
|
}
|
|
5351
5717
|
|
|
5352
5718
|
// src/providers/openrouter/commands/video-status.ts
|
|
5353
|
-
import
|
|
5354
|
-
import
|
|
5719
|
+
import fs36 from "fs";
|
|
5720
|
+
import path41 from "path";
|
|
5355
5721
|
async function downloadOpenRouterVideo(opts) {
|
|
5356
5722
|
const { jobId, logger, source } = opts;
|
|
5357
5723
|
const apiKey = requireOpenRouterKey();
|
|
5358
5724
|
const url = videoContentUrl(jobId);
|
|
5359
|
-
const dest =
|
|
5725
|
+
const dest = path41.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
5360
5726
|
const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
5361
5727
|
if (!r.ok) {
|
|
5362
5728
|
logger.error(`Download HTTP ${r.status}`);
|
|
@@ -5366,14 +5732,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
5366
5732
|
logger.error("Empty response body");
|
|
5367
5733
|
process.exit(1);
|
|
5368
5734
|
}
|
|
5369
|
-
|
|
5370
|
-
const ws =
|
|
5735
|
+
fs36.mkdirSync(path41.dirname(dest), { recursive: true });
|
|
5736
|
+
const ws = fs36.createWriteStream(dest);
|
|
5371
5737
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
5372
5738
|
await pipeline2(r.body, ws);
|
|
5373
5739
|
logger.success(`Saved ${dest}`);
|
|
5374
5740
|
if (opts.output) {
|
|
5375
|
-
|
|
5376
|
-
|
|
5741
|
+
fs36.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
|
|
5742
|
+
fs36.copyFileSync(dest, opts.output);
|
|
5377
5743
|
logger.success(`Copied to ${opts.output}`);
|
|
5378
5744
|
}
|
|
5379
5745
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -5664,6 +6030,16 @@ function resolveProviderKey(p) {
|
|
|
5664
6030
|
}
|
|
5665
6031
|
return {};
|
|
5666
6032
|
}
|
|
6033
|
+
function cloudflareReadiness() {
|
|
6034
|
+
const accountId = resolveKey("CLOUDFLARE_ACCOUNT_ID")?.trim();
|
|
6035
|
+
const apiToken = resolveKey("CLOUDFLARE_API_TOKEN")?.trim();
|
|
6036
|
+
const gatewayId = resolveKey("CLOUDFLARE_AI_GATEWAY_ID")?.trim();
|
|
6037
|
+
const replicateToken = resolveKey("REPLICATE_API_TOKEN")?.trim();
|
|
6038
|
+
return {
|
|
6039
|
+
native: Boolean(accountId && apiToken),
|
|
6040
|
+
video: Boolean(accountId && apiToken && gatewayId && replicateToken)
|
|
6041
|
+
};
|
|
6042
|
+
}
|
|
5667
6043
|
async function checkCodexImageReadiness(hasAnyProviderKey, verbose = false, checker = checkCodexAuthenticated) {
|
|
5668
6044
|
if (hasAnyProviderKey && !verbose) return false;
|
|
5669
6045
|
return checker();
|
|
@@ -5704,6 +6080,20 @@ function registerCheckCommand(program2) {
|
|
|
5704
6080
|
logger.warn(`${p.envPrimary} not set (${p.name} features unavailable)`);
|
|
5705
6081
|
}
|
|
5706
6082
|
}
|
|
6083
|
+
const cloudflare = cloudflareReadiness();
|
|
6084
|
+
if (cloudflare.native) {
|
|
6085
|
+
anyKey = true;
|
|
6086
|
+
logger.success("Cloudflare Workers AI credentials configured (image and speech)");
|
|
6087
|
+
if (cloudflare.video) {
|
|
6088
|
+
logger.success("Cloudflare AI Gateway video credentials configured");
|
|
6089
|
+
} else {
|
|
6090
|
+
logger.info("Cloudflare video needs CLOUDFLARE_AI_GATEWAY_ID and REPLICATE_API_TOKEN");
|
|
6091
|
+
}
|
|
6092
|
+
} else {
|
|
6093
|
+
logger.info(
|
|
6094
|
+
"Cloudflare media needs CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN (optional)"
|
|
6095
|
+
);
|
|
6096
|
+
}
|
|
5707
6097
|
const shouldCheckCodex = !anyKey || opts.verbose;
|
|
5708
6098
|
let codexReady = false;
|
|
5709
6099
|
if (shouldCheckCodex) {
|
|
@@ -5760,8 +6150,8 @@ function registerCheckCommand(program2) {
|
|
|
5760
6150
|
|
|
5761
6151
|
// src/commands/doc/convert.ts
|
|
5762
6152
|
init_errors();
|
|
5763
|
-
import
|
|
5764
|
-
import
|
|
6153
|
+
import fs37 from "fs";
|
|
6154
|
+
import path42 from "path";
|
|
5765
6155
|
|
|
5766
6156
|
// src/commands/doc/default-prompt.ts
|
|
5767
6157
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -5782,18 +6172,18 @@ async function convertDocuments(opts) {
|
|
|
5782
6172
|
const model = opts.model ?? DOC_MODEL_DEFAULT;
|
|
5783
6173
|
const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
|
|
5784
6174
|
for (const f of inputs) {
|
|
5785
|
-
if (!
|
|
6175
|
+
if (!fs37.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
|
|
5786
6176
|
}
|
|
5787
6177
|
let outputPath;
|
|
5788
6178
|
if (output) {
|
|
5789
|
-
outputPath =
|
|
6179
|
+
outputPath = path42.resolve(output);
|
|
5790
6180
|
} else if (autoName && inputs.length === 1 && inputs[0]) {
|
|
5791
|
-
const base =
|
|
5792
|
-
outputPath =
|
|
6181
|
+
const base = path42.basename(inputs[0], path42.extname(inputs[0]));
|
|
6182
|
+
outputPath = path42.join(getOutputDir(), `${base}-extraction.md`);
|
|
5793
6183
|
} else {
|
|
5794
|
-
outputPath =
|
|
6184
|
+
outputPath = path42.join(getOutputDir(), "document-extraction.md");
|
|
5795
6185
|
}
|
|
5796
|
-
|
|
6186
|
+
fs37.mkdirSync(path42.dirname(outputPath), { recursive: true });
|
|
5797
6187
|
const results = [];
|
|
5798
6188
|
for (let i = 0; i < inputs.length; i++) {
|
|
5799
6189
|
const filePath = inputs[i];
|
|
@@ -5844,7 +6234,7 @@ async function convertDocuments(opts) {
|
|
|
5844
6234
|
`);
|
|
5845
6235
|
for (const r of results) {
|
|
5846
6236
|
lines.push(`
|
|
5847
|
-
## ${
|
|
6237
|
+
## ${path42.basename(r.file)}
|
|
5848
6238
|
`);
|
|
5849
6239
|
if (r.status === "success" && r.markdown) {
|
|
5850
6240
|
lines.push(`${r.markdown}
|
|
@@ -5857,7 +6247,7 @@ async function convertDocuments(opts) {
|
|
|
5857
6247
|
}
|
|
5858
6248
|
lines.push("\n---\n");
|
|
5859
6249
|
}
|
|
5860
|
-
|
|
6250
|
+
fs37.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
5861
6251
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
5862
6252
|
console.log(`
|
|
5863
6253
|
${"=".repeat(50)}`);
|
|
@@ -5905,12 +6295,12 @@ function registerDocCommands(program2) {
|
|
|
5905
6295
|
|
|
5906
6296
|
// src/commands/media/index.ts
|
|
5907
6297
|
init_errors();
|
|
5908
|
-
import
|
|
5909
|
-
import
|
|
6298
|
+
import fs40 from "fs";
|
|
6299
|
+
import path45 from "path";
|
|
5910
6300
|
|
|
5911
6301
|
// src/commands/media/batch.ts
|
|
5912
|
-
import
|
|
5913
|
-
import
|
|
6302
|
+
import fs38 from "fs";
|
|
6303
|
+
import path43 from "path";
|
|
5914
6304
|
|
|
5915
6305
|
// src/commands/media/detect-type.ts
|
|
5916
6306
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
@@ -6106,10 +6496,10 @@ async function batchOptimize(opts) {
|
|
|
6106
6496
|
verbose,
|
|
6107
6497
|
logger
|
|
6108
6498
|
} = opts;
|
|
6109
|
-
|
|
6110
|
-
const entries =
|
|
6499
|
+
fs38.mkdirSync(outputDir, { recursive: true });
|
|
6500
|
+
const entries = fs38.readdirSync(inputDir);
|
|
6111
6501
|
const mediaFiles = entries.filter((f) => {
|
|
6112
|
-
const ext =
|
|
6502
|
+
const ext = path43.extname(f).toLowerCase();
|
|
6113
6503
|
return ALL_EXTS.has(ext);
|
|
6114
6504
|
});
|
|
6115
6505
|
if (mediaFiles.length === 0) {
|
|
@@ -6120,9 +6510,9 @@ async function batchOptimize(opts) {
|
|
|
6120
6510
|
let succeeded = 0;
|
|
6121
6511
|
const errors = [];
|
|
6122
6512
|
for (const file of mediaFiles) {
|
|
6123
|
-
const inputPath =
|
|
6124
|
-
const outputPath =
|
|
6125
|
-
const ext =
|
|
6513
|
+
const inputPath = path43.join(inputDir, file);
|
|
6514
|
+
const outputPath = path43.join(outputDir, file);
|
|
6515
|
+
const ext = path43.extname(file).toLowerCase();
|
|
6126
6516
|
const kind = extToKind(ext);
|
|
6127
6517
|
logger?.debug(`Processing: ${file}`);
|
|
6128
6518
|
try {
|
|
@@ -6159,8 +6549,8 @@ async function batchOptimize(opts) {
|
|
|
6159
6549
|
|
|
6160
6550
|
// src/commands/media/split-video.ts
|
|
6161
6551
|
init_errors();
|
|
6162
|
-
import
|
|
6163
|
-
import
|
|
6552
|
+
import fs39 from "fs";
|
|
6553
|
+
import path44 from "path";
|
|
6164
6554
|
async function splitVideo(opts) {
|
|
6165
6555
|
await assertFfmpeg();
|
|
6166
6556
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -6171,10 +6561,10 @@ async function splitVideo(opts) {
|
|
|
6171
6561
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
6172
6562
|
return [input];
|
|
6173
6563
|
}
|
|
6174
|
-
|
|
6175
|
-
const ext =
|
|
6176
|
-
const baseName =
|
|
6177
|
-
const outputPattern =
|
|
6564
|
+
fs39.mkdirSync(outputDir, { recursive: true });
|
|
6565
|
+
const ext = path44.extname(input) || ".mp4";
|
|
6566
|
+
const baseName = path44.basename(input, ext);
|
|
6567
|
+
const outputPattern = path44.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
6178
6568
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
6179
6569
|
const args = [
|
|
6180
6570
|
"-i",
|
|
@@ -6193,8 +6583,8 @@ async function splitVideo(opts) {
|
|
|
6193
6583
|
await runFfmpeg(args, { verbose, logger });
|
|
6194
6584
|
const files = [];
|
|
6195
6585
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
6196
|
-
const fname =
|
|
6197
|
-
if (
|
|
6586
|
+
const fname = path44.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
6587
|
+
if (fs39.existsSync(fname)) files.push(fname);
|
|
6198
6588
|
}
|
|
6199
6589
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
6200
6590
|
return files;
|
|
@@ -6208,9 +6598,9 @@ function registerMediaCommands(program2) {
|
|
|
6208
6598
|
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(
|
|
6209
6599
|
async (opts) => {
|
|
6210
6600
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
6211
|
-
if (!
|
|
6212
|
-
|
|
6213
|
-
const ext =
|
|
6601
|
+
if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
6602
|
+
fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
|
|
6603
|
+
const ext = path45.extname(opts.input).toLowerCase();
|
|
6214
6604
|
const kind = extToKind(ext);
|
|
6215
6605
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
6216
6606
|
const quality = Number.parseInt(opts.quality, 10);
|
|
@@ -6250,7 +6640,7 @@ function registerMediaCommands(program2) {
|
|
|
6250
6640
|
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(
|
|
6251
6641
|
async (opts) => {
|
|
6252
6642
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
6253
|
-
if (!
|
|
6643
|
+
if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
6254
6644
|
const chunks = await splitVideo({
|
|
6255
6645
|
input: opts.input,
|
|
6256
6646
|
outputDir: opts.outputDir,
|
|
@@ -6266,7 +6656,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
6266
6656
|
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(
|
|
6267
6657
|
async (opts) => {
|
|
6268
6658
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
6269
|
-
if (!
|
|
6659
|
+
if (!fs40.existsSync(opts.inputDir))
|
|
6270
6660
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
6271
6661
|
const result = await batchOptimize({
|
|
6272
6662
|
inputDir: opts.inputDir,
|
|
@@ -6296,6 +6686,7 @@ function registerCommands(program2) {
|
|
|
6296
6686
|
registerOpenRouterCommands(program2);
|
|
6297
6687
|
registerLeonardoCommands(program2);
|
|
6298
6688
|
registerBytePlusCommands(program2);
|
|
6689
|
+
registerCloudflareCommands(program2);
|
|
6299
6690
|
registerElevenLabsCommands(program2);
|
|
6300
6691
|
registerMediaCommands(program2);
|
|
6301
6692
|
registerDocCommands(program2);
|