@mrgoonie/multix 0.1.1 → 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/dist/cli.js CHANGED
@@ -414,9 +414,9 @@ var BytePlusClient = class {
414
414
  }
415
415
  apiKey;
416
416
  baseUrl;
417
- async request(path45, opts = {}) {
417
+ async request(path46, opts = {}) {
418
418
  const base = this.baseUrl.replace(/\/$/, "");
419
- const url = new URL(base + path45);
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(path45, query, logger) {
438
- return this.request(path45, { method: "GET", query, logger });
437
+ get(path46, query, logger) {
438
+ return this.request(path46, { method: "GET", query, logger });
439
439
  }
440
- post(path45, body, logger) {
441
- return this.request(path45, { method: "POST", body, logger });
440
+ post(path46, body, logger) {
441
+ return this.request(path46, { method: "POST", body, logger });
442
442
  }
443
- delete(path45, logger) {
444
- return this.request(path45, { method: "DELETE", logger });
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: path45, role } = parseRefSpec(spec);
994
- if (kind === "image") return toRef(kind, await resolveImageInput(path45, logger), role);
995
- if (kind === "video") return toRef(kind, await resolveVideoInput(path45, logger), role);
996
- return toRef(kind, await resolveAudioInput(path45, logger), role);
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 videoUrl = content?.video_url;
1134
+ const videoUrl2 = content?.video_url;
1135
1135
  const fileUrl = content?.file_url;
1136
- if (videoUrl) {
1136
+ if (videoUrl2) {
1137
1137
  const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1138
- await downloadVideo(videoUrl, outPath, logger);
1138
+ await downloadVideo(videoUrl2, outPath, logger);
1139
1139
  await maybeDownloadThumb(status, outPath, {
1140
1140
  skip: opts.thumb === false,
1141
1141
  copyTo: opts.output,
@@ -1240,7 +1240,7 @@ Task: ${taskId}`);
1240
1240
  // src/providers/byteplus/commands/index.ts
1241
1241
  function registerBytePlusCommands(program2) {
1242
1242
  const byteplus = program2.command("byteplus").description(
1243
- "BytePlus ModelArk: Seedream image, Seedance video, Hyper3D / Hitem3d 3D generation"
1243
+ "BytePlus ModelArk: image (Seedream 4.0), video (Seedance 2.0 text/image/reference-to-video), and 3D (Hyper3D / Hitem3d)"
1244
1244
  );
1245
1245
  registerBytePlusGenerateCommand(byteplus);
1246
1246
  registerBytePlusImageToImageCommand(byteplus);
@@ -1251,9 +1251,375 @@ function registerBytePlusCommands(program2) {
1251
1251
  registerBytePlusStatusCommand(byteplus);
1252
1252
  }
1253
1253
 
1254
- // src/providers/elevenlabs/client.ts
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 = path12.resolve(filePath);
1419
- if (!fs7.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
1420
- const buf = fs7.readFileSync(abs);
1421
- const name = fieldFilename ?? path12.basename(abs);
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 saveBytes(opts) {
1808
+ function saveBytes2(opts) {
1443
1809
  const { bytes, task, ext, outputCopy, logger } = opts;
1444
1810
  const dir = getOutputDir();
1445
- const dest = path12.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
1446
- fs7.writeFileSync(dest, bytes);
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
- fs7.mkdirSync(path12.dirname(path12.resolve(outputCopy)), { recursive: true });
1450
- fs7.copyFileSync(dest, outputCopy);
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 fs8 from "fs";
1490
- import path13 from "path";
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 = fs8.readFileSync(path13.resolve(opts.textFile), "utf8");
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 ? path13.resolve(opts.output) : path13.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
1505
- fs8.mkdirSync(path13.dirname(dest), { recursive: true });
1506
- fs8.writeFileSync(dest, JSON.stringify(data, null, 2));
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 fs9 from "fs";
1550
- import path14 from "path";
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 ? path14.resolve(opts.output) : path14.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
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 ? path14.resolve(opts.output) : path14.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
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
- fs9.mkdirSync(path14.dirname(dest), { recursive: true });
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
- fs9.writeFileSync(dest, arr);
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 = saveBytes({
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 fs10 from "fs";
1655
- import path15 from "path";
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 = path15.resolve(opts.plan);
1671
- if (!fs10.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
1672
- const planJson = JSON.parse(fs10.readFileSync(planPath, "utf8"));
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 = saveBytes({
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 = saveBytes({
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 fs11 from "fs";
1732
- import path16 from "path";
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 ? path16.resolve(opts.output) : path16.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
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
- fs11.mkdirSync(path16.dirname(dest), { recursive: true });
1771
- fs11.writeFileSync(dest, body);
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 = saveBytes({
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 = saveBytes({
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 = saveBytes({
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),
@@ -1998,7 +2364,7 @@ ${data.voices.length} voices${data.has_more ? " (more available)" : ""}`);
1998
2364
  // src/providers/elevenlabs/commands/index.ts
1999
2365
  function registerElevenLabsCommands(program2) {
2000
2366
  const el = program2.command("elevenlabs").description(
2001
- "ElevenLabs: TTS, voices, cloning, STT, voice changer, SFX, music, dubbing, isolation, alignment"
2367
+ "ElevenLabs: text-to-speech, voice cloning, speech-to-text, voice changer, sound effects, music, dubbing, voice isolation, forced alignment"
2002
2368
  );
2003
2369
  registerTtsCommand(el);
2004
2370
  registerVoicesCommand(el);
@@ -2015,16 +2381,16 @@ function registerElevenLabsCommands(program2) {
2015
2381
 
2016
2382
  // src/providers/gemini/commands/analyze.ts
2017
2383
  init_errors();
2018
- import fs14 from "fs";
2384
+ import fs15 from "fs";
2019
2385
 
2020
2386
  // src/providers/gemini/client.ts
2021
- import fs12 from "fs";
2022
- import path18 from "path";
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 path17 from "path";
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 = path17.extname(filePath).toLowerCase();
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 = fs12.readFileSync(filePath);
2095
- const displayName = path18.basename(filePath);
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 = fs12.readFileSync(filePath).toString("base64");
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 = fs12.statSync(filePath);
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 fs13 from "fs";
2243
- import path19 from "path";
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
- fs13.mkdirSync(path19.dirname(path19.resolve(outputPath)), { recursive: true });
2248
- fs13.writeFileSync(outputPath, content, "utf8");
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 (!fs14.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
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 fs15 from "fs";
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 (!fs15.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
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 fs16 from "fs";
2411
- import path20 from "path";
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 = path20.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
2495
- fs16.writeFileSync(dest, outBytes);
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
- fs16.mkdirSync(path20.dirname(path20.resolve(output)), { recursive: true });
2499
- fs16.copyFileSync(dest, output);
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 fs17 from "fs";
2599
- import path21 from "path";
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 (!fs17.existsSync(imgPath))
2999
+ if (!fs18.existsSync(imgPath))
2634
3000
  throw new ValidationError(`Reference image not found: ${imgPath}`);
2635
- const data = fs17.readFileSync(imgPath).toString("base64");
2636
- const ext = path21.extname(imgPath).toLowerCase();
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 = path21.join(outDir, `veo_generated_${Date.now()}.mp4`);
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
- fs17.mkdirSync(path21.dirname(path21.resolve(opts.output)), { recursive: true });
2691
- fs17.copyFileSync(dest, opts.output);
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 fs18 from "fs";
2711
- import path22 from "path";
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 = path22.join(outDir, fname);
2754
- fs18.writeFileSync(dest, Buffer.from(img.data, "base64"));
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
- fs18.mkdirSync(path22.dirname(path22.resolve(opts.output)), { recursive: true });
2770
- fs18.copyFileSync(savedFiles[0], opts.output);
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 fs19 from "fs";
2782
- import path23 from "path";
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 = path23.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
2844
- fs19.writeFileSync(dest, Buffer.from(img.data, "base64"));
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
- fs19.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
2850
- fs19.copyFileSync(saved[0], opts.output);
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 = path23.extname(new URL(url).pathname).toLowerCase();
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 fs20 from "fs";
2886
- import path24 from "path";
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 (!fs20.existsSync(imgPath)) {
3257
+ if (!fs21.existsSync(imgPath)) {
2892
3258
  throw new ValidationError(`Image not found: ${imgPath}`);
2893
3259
  }
2894
- const data = fs20.readFileSync(imgPath).toString("base64");
2895
- const ext = path24.extname(imgPath).toLowerCase();
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 = path24.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
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
- fs20.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
2973
- fs20.copyFileSync(dest, opts.output);
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 fs21 from "fs";
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 (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
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++) {
@@ -3037,7 +3403,7 @@ function registerTranscribeCommand2(parent) {
3037
3403
  // src/providers/gemini/commands/index.ts
3038
3404
  function registerGeminiCommands(program2) {
3039
3405
  const gemini = program2.command("gemini").description(
3040
- "Gemini AI operations: analyze, transcribe, extract, generate images/video/speech"
3406
+ "Gemini: analyze, transcribe, extract data, generate images (Imagen/Nano Banana), video (Veo), and speech (Flash TTS)"
3041
3407
  );
3042
3408
  registerAnalyzeCommand(gemini);
3043
3409
  registerTranscribeCommand2(gemini);
@@ -3051,8 +3417,8 @@ function registerGeminiCommands(program2) {
3051
3417
 
3052
3418
  // src/providers/leonardo/commands/generate.ts
3053
3419
  init_http_client();
3054
- import fs22 from "fs";
3055
- import path25 from "path";
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(path45, opts = {}) {
3495
+ async request(path46, opts = {}) {
3130
3496
  const base = this.baseUrl.replace(/\/$/, "");
3131
- const v2Override = path45.startsWith("v2:");
3497
+ const v2Override = path46.startsWith("v2:");
3132
3498
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
3133
- const finalPath = v2Override ? path45.slice(3) : path45;
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(path45, query, logger) {
3153
- return this.request(path45, { method: "GET", query, logger });
3518
+ get(path46, query, logger) {
3519
+ return this.request(path46, { method: "GET", query, logger });
3154
3520
  }
3155
- post(path45, body, logger) {
3156
- return this.request(path45, { method: "POST", body, logger });
3521
+ post(path46, body, logger) {
3522
+ return this.request(path46, { method: "POST", body, logger });
3157
3523
  }
3158
- delete(path45, logger) {
3159
- return this.request(path45, { method: "DELETE", logger });
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 = path25.extname(u.pathname);
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 = path25.join(outDir, filename);
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 = path25.resolve(opts.output);
3266
- fs22.mkdirSync(path25.dirname(dest), { recursive: true });
3267
- fs22.copyFileSync(saved[0], dest);
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 fs23 from "fs";
3280
- import path26 from "path";
3645
+ import fs24 from "fs";
3646
+ import path27 from "path";
3281
3647
  function extFromUrl2(url, fallback = ".png") {
3282
3648
  try {
3283
- return path26.extname(new URL(url).pathname) || fallback;
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 = path26.join(outDir, filename);
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 = path26.resolve(opts.output);
3356
- fs23.mkdirSync(path26.dirname(dest), { recursive: true });
3357
- fs23.copyFileSync(saved[0], dest);
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 path27 from "path";
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 videoUrl = gen.generated_images?.find(
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 videoUrl === "string") console.log(videoUrl);
3839
+ if (typeof videoUrl2 === "string") console.log(videoUrl2);
3474
3840
  return;
3475
3841
  }
3476
- if (typeof videoUrl !== "string") {
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 ?? path27.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
3481
- await downloadFile(videoUrl, outPath);
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 path28 from "path";
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 : path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
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 = path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
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++;
@@ -3722,7 +4088,9 @@ Resolutions: ${LEONARDO_VIDEO_RESOLUTIONS.join(", ")}`);
3722
4088
 
3723
4089
  // src/providers/leonardo/commands/index.ts
3724
4090
  function registerLeonardoCommands(program2) {
3725
- const leonardo = program2.command("leonardo").description("Leonardo.Ai: image/video generation, upscaling, account info");
4091
+ const leonardo = program2.command("leonardo").description(
4092
+ "Leonardo: image generation, video (Motion, Veo, Kling, Hailuo, Seedance, LTX), upscaling, and account info"
4093
+ );
3726
4094
  registerLeonardoGenerateCommand(leonardo);
3727
4095
  registerLeonardoImageToImageCommand(leonardo);
3728
4096
  registerLeonardoVideoCommand(leonardo);
@@ -3819,8 +4187,8 @@ function sleep4(ms) {
3819
4187
 
3820
4188
  // src/providers/minimax/generators/music.ts
3821
4189
  init_http_client();
3822
- import fs24 from "fs";
3823
- import path29 from "path";
4190
+ import fs25 from "fs";
4191
+ import path30 from "path";
3824
4192
  async function generateMinimaxMusic(opts) {
3825
4193
  const {
3826
4194
  apiKey,
@@ -3865,14 +4233,14 @@ async function generateMinimaxMusic(opts) {
3865
4233
  }
3866
4234
  const durationMs = resp.extra_info?.music_duration ?? 0;
3867
4235
  const outDir = getOutputDir();
3868
- const dest = path29.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
4236
+ const dest = path30.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3869
4237
  try {
3870
4238
  if (audioData.startsWith("http")) {
3871
4239
  const bytes = await fetchBytes(audioData);
3872
- fs24.writeFileSync(dest, bytes);
4240
+ fs25.writeFileSync(dest, bytes);
3873
4241
  } else {
3874
4242
  const audioBytes = Buffer.from(audioData, "hex");
3875
- fs24.writeFileSync(dest, audioBytes);
4243
+ fs25.writeFileSync(dest, audioBytes);
3876
4244
  }
3877
4245
  } catch (e) {
3878
4246
  return {
@@ -3883,8 +4251,8 @@ async function generateMinimaxMusic(opts) {
3883
4251
  const durSec = durationMs / 1e3;
3884
4252
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
3885
4253
  if (output) {
3886
- fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3887
- fs24.copyFileSync(dest, output);
4254
+ fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4255
+ fs25.copyFileSync(dest, output);
3888
4256
  }
3889
4257
  return { status: "success", generatedAudio: dest, durationMs, model };
3890
4258
  }
@@ -3958,8 +4326,8 @@ Generated music: ${result.generatedAudio}`);
3958
4326
  init_errors();
3959
4327
 
3960
4328
  // src/providers/minimax/generators/speech.ts
3961
- import fs25 from "fs";
3962
- import path30 from "path";
4329
+ import fs26 from "fs";
4330
+ import path31 from "path";
3963
4331
  async function generateMinimaxSpeech(opts) {
3964
4332
  const {
3965
4333
  apiKey,
@@ -4006,12 +4374,12 @@ async function generateMinimaxSpeech(opts) {
4006
4374
  const audioBytes = Buffer.from(audioHex, "hex");
4007
4375
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
4008
4376
  const outDir = getOutputDir();
4009
- const dest = path30.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4010
- fs25.writeFileSync(dest, audioBytes);
4377
+ const dest = path31.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4378
+ fs26.writeFileSync(dest, audioBytes);
4011
4379
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
4012
4380
  if (output) {
4013
- fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4014
- fs25.copyFileSync(dest, output);
4381
+ fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4382
+ fs26.copyFileSync(dest, output);
4015
4383
  }
4016
4384
  return { status: "success", generatedAudio: dest, model };
4017
4385
  }
@@ -4058,8 +4426,8 @@ Generated audio: ${result.generatedAudio}`);
4058
4426
  init_errors();
4059
4427
 
4060
4428
  // src/providers/minimax/generators/video.ts
4061
- import fs26 from "fs";
4062
- import path31 from "path";
4429
+ import fs27 from "fs";
4430
+ import path32 from "path";
4063
4431
  async function generateMinimaxVideo(opts) {
4064
4432
  const {
4065
4433
  apiKey,
@@ -4105,18 +4473,18 @@ async function generateMinimaxVideo(opts) {
4105
4473
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
4106
4474
  }
4107
4475
  const outDir = getOutputDir();
4108
- const dest = path31.join(outDir, `minimax_video_${Date.now()}.mp4`);
4476
+ const dest = path32.join(outDir, `minimax_video_${Date.now()}.mp4`);
4109
4477
  try {
4110
4478
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
4111
4479
  } catch (e) {
4112
4480
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
4113
4481
  }
4114
4482
  const elapsed = (Date.now() - start) / 1e3;
4115
- const stat = fs26.statSync(dest);
4483
+ const stat = fs27.statSync(dest);
4116
4484
  const fileSizeMb = stat.size / (1024 * 1024);
4117
4485
  if (output) {
4118
- fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4119
- fs26.copyFileSync(dest, output);
4486
+ fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4487
+ fs27.copyFileSync(dest, output);
4120
4488
  }
4121
4489
  await maybeDownloadThumb(pollResult, dest, {
4122
4490
  skip: thumb === false,
@@ -4179,8 +4547,8 @@ Generated video: ${result.generatedVideo}`);
4179
4547
  // src/providers/minimax/generators/image.ts
4180
4548
  init_errors();
4181
4549
  init_http_client();
4182
- import fs27 from "fs";
4183
- import path32 from "path";
4550
+ import fs28 from "fs";
4551
+ import path33 from "path";
4184
4552
  async function generateMinimaxImage(opts) {
4185
4553
  const {
4186
4554
  apiKey,
@@ -4218,8 +4586,8 @@ async function generateMinimaxImage(opts) {
4218
4586
  try {
4219
4587
  const bytes = await fetchBytes(url);
4220
4588
  const fname = `minimax_image_${Date.now()}_${i}.png`;
4221
- const dest = path32.join(outDir, fname);
4222
- fs27.writeFileSync(dest, bytes);
4589
+ const dest = path33.join(outDir, fname);
4590
+ fs28.writeFileSync(dest, bytes);
4223
4591
  savedFiles.push(dest);
4224
4592
  logger?.success(`Saved: ${dest}`);
4225
4593
  } catch (e) {
@@ -4230,8 +4598,8 @@ async function generateMinimaxImage(opts) {
4230
4598
  }
4231
4599
  }
4232
4600
  if (output && savedFiles[0]) {
4233
- fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4234
- fs27.copyFileSync(savedFiles[0], output);
4601
+ fs28.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
4602
+ fs28.copyFileSync(savedFiles[0], output);
4235
4603
  }
4236
4604
  return { status: "success", generatedImages: savedFiles, model };
4237
4605
  }
@@ -4267,8 +4635,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
4267
4635
  // src/providers/minimax/commands/image-to-image.ts
4268
4636
  init_errors();
4269
4637
  init_http_client();
4270
- import fs28 from "fs";
4271
- import path33 from "path";
4638
+ import fs29 from "fs";
4639
+ import path34 from "path";
4272
4640
  function registerMinimaxImageToImageCommand(parent) {
4273
4641
  parent.command("image-to-image").alias("i2i").description(
4274
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."
@@ -4323,8 +4691,8 @@ function registerMinimaxImageToImageCommand(parent) {
4323
4691
  if (!url) continue;
4324
4692
  try {
4325
4693
  const bytes = await fetchBytes(url);
4326
- const dest = path33.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4327
- fs28.writeFileSync(dest, bytes);
4694
+ const dest = path34.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4695
+ fs29.writeFileSync(dest, bytes);
4328
4696
  saved.push(dest);
4329
4697
  logger.success(`Saved: ${dest}`);
4330
4698
  } catch (e) {
@@ -4335,8 +4703,8 @@ function registerMinimaxImageToImageCommand(parent) {
4335
4703
  }
4336
4704
  }
4337
4705
  if (opts.output && saved[0]) {
4338
- fs28.mkdirSync(path33.dirname(path33.resolve(opts.output)), { recursive: true });
4339
- fs28.copyFileSync(saved[0], opts.output);
4706
+ fs29.mkdirSync(path34.dirname(path34.resolve(opts.output)), { recursive: true });
4707
+ fs29.copyFileSync(saved[0], opts.output);
4340
4708
  logger.success(`Copied to: ${opts.output}`);
4341
4709
  }
4342
4710
  console.log(`
@@ -4348,7 +4716,7 @@ Generated ${saved.length} image(s):`);
4348
4716
 
4349
4717
  // src/providers/minimax/commands/index.ts
4350
4718
  function registerMinimaxCommands(program2) {
4351
- const minimax = program2.command("minimax").description("MiniMax generation: images, video, speech, and music");
4719
+ const minimax = program2.command("minimax").description("MiniMax: generate images, video (Hailuo), speech (TTS), and music");
4352
4720
  registerMinimaxGenerateCommand(minimax);
4353
4721
  registerMinimaxImageToImageCommand(minimax);
4354
4722
  registerMinimaxGenerateVideoCommand(minimax);
@@ -4470,12 +4838,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
4470
4838
 
4471
4839
  // src/providers/openai/openai-audio.ts
4472
4840
  init_http_client();
4473
- import fs30 from "fs";
4474
- import path35 from "path";
4841
+ import fs31 from "fs";
4842
+ import path36 from "path";
4475
4843
 
4476
4844
  // src/providers/openai/client.ts
4477
- import fs29 from "fs";
4478
- import path34 from "path";
4845
+ import fs30 from "fs";
4846
+ import path35 from "path";
4479
4847
  init_errors();
4480
4848
  init_http_client();
4481
4849
  var DEFAULT_TIMEOUT_MS3 = 24e4;
@@ -4542,10 +4910,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
4542
4910
  return Buffer.from(await res.arrayBuffer());
4543
4911
  }
4544
4912
  function fileFromPath(filePath) {
4545
- const abs = path34.resolve(filePath);
4546
- if (!fs29.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4547
- const bytes = fs29.readFileSync(abs);
4548
- return new File([new Uint8Array(bytes)], path34.basename(abs));
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));
4549
4917
  }
4550
4918
 
4551
4919
  // src/providers/openai/openai-audio.ts
@@ -4584,7 +4952,7 @@ async function resolveAudioReferenceDataUrl(ref) {
4584
4952
  }
4585
4953
  function audioMimeFromReference(ref) {
4586
4954
  const pathname = new URL(ref).pathname;
4587
- const ext = path35.extname(pathname).toLowerCase();
4955
+ const ext = path36.extname(pathname).toLowerCase();
4588
4956
  switch (ext) {
4589
4957
  case ".mp3":
4590
4958
  return "audio/mpeg";
@@ -4603,12 +4971,12 @@ function audioMimeFromReference(ref) {
4603
4971
  async function generateOpenAISpeech(opts) {
4604
4972
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
4605
4973
  const ext = extFromAudioFormat(opts.outputFormat);
4606
- const dest = path35.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4607
- fs30.writeFileSync(dest, bytes);
4974
+ const dest = path36.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4975
+ fs31.writeFileSync(dest, bytes);
4608
4976
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
4609
4977
  if (opts.output) {
4610
- fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4611
- fs30.copyFileSync(dest, opts.output);
4978
+ fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
4979
+ fs31.copyFileSync(dest, opts.output);
4612
4980
  }
4613
4981
  return dest;
4614
4982
  }
@@ -4630,9 +4998,9 @@ async function transcribeOpenAIAudio(opts) {
4630
4998
  );
4631
4999
  const text = typeof data.text === "string" ? data.text : void 0;
4632
5000
  const ext = opts.format === "text" ? "txt" : "json";
4633
- const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
4634
- fs30.mkdirSync(path35.dirname(dest), { recursive: true });
4635
- fs30.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
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));
4636
5004
  opts.logger?.success(`Saved transcript: ${dest}`);
4637
5005
  return { path: dest, text };
4638
5006
  }
@@ -4659,9 +5027,9 @@ Generated audio: ${dest}`);
4659
5027
  }
4660
5028
 
4661
5029
  // src/providers/openai/codex-image-driver.ts
4662
- import fs31 from "fs";
5030
+ import fs32 from "fs";
4663
5031
  import os2 from "os";
4664
- import path36 from "path";
5032
+ import path37 from "path";
4665
5033
  import { execa } from "execa";
4666
5034
  init_errors();
4667
5035
  var defaultRunner = async (cmd, args, opts) => {
@@ -4710,12 +5078,12 @@ function sanitizeCodexEnv() {
4710
5078
  async function runCodexImageGeneration(opts) {
4711
5079
  const runner = opts.runner ?? defaultRunner;
4712
5080
  const ext = extFromImageFormat(opts.outputFormat);
4713
- const dest = opts.output ? path36.resolve(opts.output) : path36.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
4714
- fs31.mkdirSync(path36.dirname(dest), { recursive: true });
4715
- const tmp = fs31.mkdtempSync(path36.join(os2.tmpdir(), "multix-codex-image-"));
4716
- const schema = path36.join(tmp, "schema.json");
4717
- const result = path36.join(tmp, "result.json");
4718
- fs31.writeFileSync(
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(
4719
5087
  schema,
4720
5088
  JSON.stringify({
4721
5089
  type: "object",
@@ -4745,7 +5113,7 @@ async function runCodexImageGeneration(opts) {
4745
5113
  if (res.exitCode !== 0) {
4746
5114
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
4747
5115
  }
4748
- if (!fs31.existsSync(dest) || fs31.statSync(dest).size === 0) {
5116
+ if (!fs32.existsSync(dest) || fs32.statSync(dest).size === 0) {
4749
5117
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
4750
5118
  }
4751
5119
  return { path: dest };
@@ -4763,8 +5131,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
4763
5131
  // src/providers/openai/openai-image.ts
4764
5132
  init_errors();
4765
5133
  init_http_client();
4766
- import fs32 from "fs";
4767
- import path37 from "path";
5134
+ import fs33 from "fs";
5135
+ import path38 from "path";
4768
5136
  function buildImageGenerationBody(opts) {
4769
5137
  return {
4770
5138
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -4797,14 +5165,14 @@ function saveOpenAIImages(opts) {
4797
5165
  for (let i = 0; i < items.length; i++) {
4798
5166
  const b64 = items[i]?.b64_json;
4799
5167
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
4800
- const dest = path37.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
4801
- fs32.writeFileSync(dest, Buffer.from(b64, "base64"));
5168
+ const dest = path38.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
5169
+ fs33.writeFileSync(dest, Buffer.from(b64, "base64"));
4802
5170
  saved.push(dest);
4803
5171
  opts.logger?.success(`Saved: ${dest}`);
4804
5172
  }
4805
5173
  if (opts.output && saved[0]) {
4806
- fs32.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
4807
- fs32.copyFileSync(saved[0], opts.output);
5174
+ fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5175
+ fs33.copyFileSync(saved[0], opts.output);
4808
5176
  opts.logger?.success(`Copied to: ${opts.output}`);
4809
5177
  }
4810
5178
  return saved;
@@ -4813,7 +5181,7 @@ async function resolveImageRefBytes(ref) {
4813
5181
  if (isHttpUrl(ref)) {
4814
5182
  const bytes2 = await fetchBytes(ref);
4815
5183
  const pathname = new URL(ref).pathname;
4816
- const filename = path37.basename(pathname) || "reference.png";
5184
+ const filename = path38.basename(pathname) || "reference.png";
4817
5185
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
4818
5186
  }
4819
5187
  const file = fileFromPath(ref);
@@ -4858,7 +5226,7 @@ function sniffImageMime2(buf, name) {
4858
5226
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
4859
5227
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
4860
5228
  return "image/webp";
4861
- const ext = path37.extname(name).toLowerCase();
5229
+ const ext = path38.extname(name).toLowerCase();
4862
5230
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
4863
5231
  if (ext === ".webp") return "image/webp";
4864
5232
  return "image/png";
@@ -4976,7 +5344,9 @@ function collect6(value, prev) {
4976
5344
 
4977
5345
  // src/providers/openai/commands/index.ts
4978
5346
  function registerOpenAICommands(program2) {
4979
- const openai = program2.command("openai").description("OpenAI image generation/editing, TTS, STT, and experimental Codex image driver");
5347
+ const openai = program2.command("openai").description(
5348
+ "OpenAI: image generation/editing (GPT-Image, Codex driver), text-to-speech (TTS), speech-to-text (STT)"
5349
+ );
4980
5350
  registerOpenAIGenerateCommand(openai);
4981
5351
  registerOpenAIImageToImageCommand(openai);
4982
5352
  registerOpenAIGenerateSpeechCommand(openai);
@@ -4984,8 +5354,8 @@ function registerOpenAICommands(program2) {
4984
5354
  }
4985
5355
 
4986
5356
  // src/providers/openrouter/client.ts
4987
- import fs33 from "fs";
4988
- import path38 from "path";
5357
+ import fs34 from "fs";
5358
+ import path39 from "path";
4989
5359
  init_errors();
4990
5360
  init_http_client();
4991
5361
 
@@ -5150,8 +5520,8 @@ async function generateOpenRouterImage(opts) {
5150
5520
  if (!url) continue;
5151
5521
  try {
5152
5522
  const bytes = await fetchBytes(url);
5153
- const dest = path38.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5154
- fs33.writeFileSync(dest, bytes);
5523
+ const dest = path39.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5524
+ fs34.writeFileSync(dest, bytes);
5155
5525
  savedFiles.push(dest);
5156
5526
  logger?.success(`Saved: ${dest}`);
5157
5527
  } catch (e) {
@@ -5162,15 +5532,17 @@ async function generateOpenRouterImage(opts) {
5162
5532
  }
5163
5533
  }
5164
5534
  if (output && savedFiles[0]) {
5165
- fs33.mkdirSync(path38.dirname(path38.resolve(output)), { recursive: true });
5166
- fs33.writeFileSync(output, fs33.readFileSync(savedFiles[0]));
5535
+ fs34.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
5536
+ fs34.writeFileSync(output, fs34.readFileSync(savedFiles[0]));
5167
5537
  }
5168
5538
  return { status: "success", generatedImages: savedFiles, model: usedModel };
5169
5539
  }
5170
5540
 
5171
5541
  // src/providers/openrouter/commands/generate.ts
5172
5542
  function registerOpenRouterGenerateCommand(parent) {
5173
- parent.command("generate").description("Generate images via OpenRouter (uses chat completions API)").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "OpenRouter model id (e.g. google/gemini-3.1-flash-image-preview)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--image-size <size>", "Image size hint (e.g. 1K, 2K)").option("--num-images <n>", "Number of images to generate", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
5543
+ parent.command("generate").description(
5544
+ "Generate images via OpenRouter (text-to-image); models: Gemini, GPT-Image, Flux, Recraft, Seedream"
5545
+ ).requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "OpenRouter model id (e.g. google/gemini-3.1-flash-image-preview)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--image-size <size>", "Image size hint (e.g. 1K, 2K)").option("--num-images <n>", "Number of images to generate", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
5174
5546
  async (opts) => {
5175
5547
  const logger = createLogger({ verbose: opts.verbose ?? false });
5176
5548
  const model = opts.model ?? resolveKey("OPENROUTER_IMAGE_MODEL") ?? DEFAULT_OPENROUTER_MODEL;
@@ -5198,15 +5570,17 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
5198
5570
  }
5199
5571
 
5200
5572
  // src/providers/openrouter/commands/image-to-image.ts
5201
- import fs34 from "fs";
5202
- import path39 from "path";
5573
+ import fs35 from "fs";
5574
+ import path40 from "path";
5203
5575
  init_errors();
5204
5576
  init_http_client();
5205
5577
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
5206
5578
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
5207
5579
  var HARD_LIMIT_BYTES2 = 8 * 1024 * 1024;
5208
5580
  function registerOpenRouterImageToImageCommand(parent) {
5209
- parent.command("image-to-image").alias("i2i").description("Edit/transform images via OpenRouter (default: google/gemini-2.5-flash-image)").requiredOption("--prompt <text>", "Text prompt describing the edit").requiredOption(
5581
+ parent.command("image-to-image").alias("i2i").description(
5582
+ "Edit/transform images via OpenRouter (image-to-image); models: Gemini, GPT-Image, Flux, Recraft, Seedream"
5583
+ ).requiredOption("--prompt <text>", "Text prompt describing the edit").requiredOption(
5210
5584
  "--ref <path>",
5211
5585
  "Reference image (URL or local path); repeatable (most models accept 1)",
5212
5586
  collect7,
@@ -5289,14 +5663,14 @@ async function runImageToImage(opts) {
5289
5663
  const url = parsed.urls[i];
5290
5664
  if (!url) continue;
5291
5665
  const bytes = await fetchBytes(url);
5292
- const dest = path39.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5293
- fs34.writeFileSync(dest, bytes);
5666
+ const dest = path40.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5667
+ fs35.writeFileSync(dest, bytes);
5294
5668
  saved.push(dest);
5295
5669
  opts.logger.success(`Saved: ${dest}`);
5296
5670
  }
5297
5671
  if (opts.output && saved[0]) {
5298
- fs34.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5299
- fs34.copyFileSync(saved[0], opts.output);
5672
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5673
+ fs35.copyFileSync(saved[0], opts.output);
5300
5674
  opts.logger.success(`Copied to: ${opts.output}`);
5301
5675
  }
5302
5676
  return saved;
@@ -5342,13 +5716,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
5342
5716
  }
5343
5717
 
5344
5718
  // src/providers/openrouter/commands/video-status.ts
5345
- import fs35 from "fs";
5346
- import path40 from "path";
5719
+ import fs36 from "fs";
5720
+ import path41 from "path";
5347
5721
  async function downloadOpenRouterVideo(opts) {
5348
5722
  const { jobId, logger, source } = opts;
5349
5723
  const apiKey = requireOpenRouterKey();
5350
5724
  const url = videoContentUrl(jobId);
5351
- const dest = path40.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5725
+ const dest = path41.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5352
5726
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
5353
5727
  if (!r.ok) {
5354
5728
  logger.error(`Download HTTP ${r.status}`);
@@ -5358,14 +5732,14 @@ async function downloadOpenRouterVideo(opts) {
5358
5732
  logger.error("Empty response body");
5359
5733
  process.exit(1);
5360
5734
  }
5361
- fs35.mkdirSync(path40.dirname(dest), { recursive: true });
5362
- const ws = fs35.createWriteStream(dest);
5735
+ fs36.mkdirSync(path41.dirname(dest), { recursive: true });
5736
+ const ws = fs36.createWriteStream(dest);
5363
5737
  const { pipeline: pipeline2 } = await import("stream/promises");
5364
5738
  await pipeline2(r.body, ws);
5365
5739
  logger.success(`Saved ${dest}`);
5366
5740
  if (opts.output) {
5367
- fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5368
- fs35.copyFileSync(dest, opts.output);
5741
+ fs36.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
5742
+ fs36.copyFileSync(dest, opts.output);
5369
5743
  logger.success(`Copied to ${opts.output}`);
5370
5744
  }
5371
5745
  await maybeDownloadThumb(source, dest, {
@@ -5376,7 +5750,7 @@ async function downloadOpenRouterVideo(opts) {
5376
5750
  return dest;
5377
5751
  }
5378
5752
  function registerOpenRouterVideoStatusCommand(parent) {
5379
- parent.command("video-status <jobId>").description("Poll an OpenRouter video generation job").option("--wait", "Poll until status is 'completed' or 'failed'").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "If completed, download the video (implies --wait)").option("--output <path>", "Copy downloaded video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
5753
+ parent.command("video-status <jobId>").description("Poll or download an OpenRouter video generation job by job ID").option("--wait", "Poll until status is 'completed' or 'failed'").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "If completed, download the video (implies --wait)").option("--output <path>", "Copy downloaded video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
5380
5754
  async (jobId, opts) => {
5381
5755
  const logger = createLogger({ verbose: opts.verbose ?? false });
5382
5756
  const shouldWait = !!(opts.wait || opts.download);
@@ -5426,7 +5800,9 @@ function registerOpenRouterVideoStatusCommand(parent) {
5426
5800
 
5427
5801
  // src/providers/openrouter/commands/image-to-video.ts
5428
5802
  function registerOpenRouterImageToVideoCommand(parent) {
5429
- parent.command("image-to-video").alias("i2v").description("Submit an image-to-video job to OpenRouter (async, returns job id)").requiredOption("--prompt <text>", "Motion/scene prompt").requiredOption("--image-url <url>", "First-frame image URL (https)").option("--last-frame-url <url>", "Optional last-frame image URL").option("--model <id>", "OpenRouter video model id (e.g. google/veo-3.1)").option("--resolution <r>", "Resolution hint (e.g. 720p, 1080p)").option("--aspect-ratio <r>", "Aspect ratio hint (e.g. 16:9, 9:16)").option("--duration <n>", "Duration seconds").option("--seed <n>", "Fixed seed").option("--wait", "Poll until status is 'completed' or 'failed'").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download the generated video on success (implies --wait)").option("--output <path>", "Copy downloaded video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
5803
+ parent.command("image-to-video").alias("i2v").description(
5804
+ "Generate video from an image via OpenRouter (image-to-video, async); models: Veo, Seedance, Kling \u2014 use video-models to list all"
5805
+ ).requiredOption("--prompt <text>", "Motion/scene prompt").requiredOption("--image-url <url>", "First-frame image URL (https)").option("--last-frame-url <url>", "Optional last-frame image URL").option("--model <id>", "OpenRouter video model id (e.g. google/veo-3.1)").option("--resolution <r>", "Resolution hint (e.g. 720p, 1080p)").option("--aspect-ratio <r>", "Aspect ratio hint (e.g. 16:9, 9:16)").option("--duration <n>", "Duration seconds").option("--seed <n>", "Fixed seed").option("--wait", "Poll until status is 'completed' or 'failed'").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download the generated video on success (implies --wait)").option("--output <path>", "Copy downloaded video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
5430
5806
  async (opts) => {
5431
5807
  const logger = createLogger({ verbose: opts.verbose ?? false });
5432
5808
  const model = opts.model ?? resolveKey("OPENROUTER_VIDEO_MODEL") ?? "google/veo-3.1";
@@ -5503,7 +5879,9 @@ function registerOpenRouterImageToVideoCommand(parent) {
5503
5879
 
5504
5880
  // src/providers/openrouter/commands/video-models.ts
5505
5881
  function registerOpenRouterVideoModelsCommand(parent) {
5506
- parent.command("video-models").description("List available OpenRouter video models").option("-v, --verbose", "Verbose logging").action(async (opts) => {
5882
+ parent.command("video-models").description(
5883
+ "List available OpenRouter video-generation models (Veo, Seedance, Kling, and others)"
5884
+ ).option("-v, --verbose", "Verbose logging").action(async (opts) => {
5507
5885
  const logger = createLogger({ verbose: opts.verbose ?? false });
5508
5886
  const res = await listVideoModels();
5509
5887
  const list = res.data ?? res.models ?? [];
@@ -5516,7 +5894,9 @@ function registerOpenRouterVideoModelsCommand(parent) {
5516
5894
 
5517
5895
  // src/providers/openrouter/commands/index.ts
5518
5896
  function registerOpenRouterCommands(program2) {
5519
- const openrouter = program2.command("openrouter").description("OpenRouter image + video generation");
5897
+ const openrouter = program2.command("openrouter").description(
5898
+ "OpenRouter: text-to-image, image-to-image, and video generation (image-to-video) via routed models \u2014 Veo, Seedance, Kling, and others"
5899
+ );
5520
5900
  registerOpenRouterGenerateCommand(openrouter);
5521
5901
  registerOpenRouterImageToImageCommand(openrouter);
5522
5902
  registerOpenRouterImageToVideoCommand(openrouter);
@@ -5650,6 +6030,16 @@ function resolveProviderKey(p) {
5650
6030
  }
5651
6031
  return {};
5652
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
+ }
5653
6043
  async function checkCodexImageReadiness(hasAnyProviderKey, verbose = false, checker = checkCodexAuthenticated) {
5654
6044
  if (hasAnyProviderKey && !verbose) return false;
5655
6045
  return checker();
@@ -5690,6 +6080,20 @@ function registerCheckCommand(program2) {
5690
6080
  logger.warn(`${p.envPrimary} not set (${p.name} features unavailable)`);
5691
6081
  }
5692
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
+ }
5693
6097
  const shouldCheckCodex = !anyKey || opts.verbose;
5694
6098
  let codexReady = false;
5695
6099
  if (shouldCheckCodex) {
@@ -5746,8 +6150,8 @@ function registerCheckCommand(program2) {
5746
6150
 
5747
6151
  // src/commands/doc/convert.ts
5748
6152
  init_errors();
5749
- import fs36 from "fs";
5750
- import path41 from "path";
6153
+ import fs37 from "fs";
6154
+ import path42 from "path";
5751
6155
 
5752
6156
  // src/commands/doc/default-prompt.ts
5753
6157
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -5768,18 +6172,18 @@ async function convertDocuments(opts) {
5768
6172
  const model = opts.model ?? DOC_MODEL_DEFAULT;
5769
6173
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
5770
6174
  for (const f of inputs) {
5771
- if (!fs36.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
6175
+ if (!fs37.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
5772
6176
  }
5773
6177
  let outputPath;
5774
6178
  if (output) {
5775
- outputPath = path41.resolve(output);
6179
+ outputPath = path42.resolve(output);
5776
6180
  } else if (autoName && inputs.length === 1 && inputs[0]) {
5777
- const base = path41.basename(inputs[0], path41.extname(inputs[0]));
5778
- outputPath = path41.join(getOutputDir(), `${base}-extraction.md`);
6181
+ const base = path42.basename(inputs[0], path42.extname(inputs[0]));
6182
+ outputPath = path42.join(getOutputDir(), `${base}-extraction.md`);
5779
6183
  } else {
5780
- outputPath = path41.join(getOutputDir(), "document-extraction.md");
6184
+ outputPath = path42.join(getOutputDir(), "document-extraction.md");
5781
6185
  }
5782
- fs36.mkdirSync(path41.dirname(outputPath), { recursive: true });
6186
+ fs37.mkdirSync(path42.dirname(outputPath), { recursive: true });
5783
6187
  const results = [];
5784
6188
  for (let i = 0; i < inputs.length; i++) {
5785
6189
  const filePath = inputs[i];
@@ -5830,7 +6234,7 @@ async function convertDocuments(opts) {
5830
6234
  `);
5831
6235
  for (const r of results) {
5832
6236
  lines.push(`
5833
- ## ${path41.basename(r.file)}
6237
+ ## ${path42.basename(r.file)}
5834
6238
  `);
5835
6239
  if (r.status === "success" && r.markdown) {
5836
6240
  lines.push(`${r.markdown}
@@ -5843,7 +6247,7 @@ async function convertDocuments(opts) {
5843
6247
  }
5844
6248
  lines.push("\n---\n");
5845
6249
  }
5846
- fs36.writeFileSync(outputPath, lines.join("\n"), "utf8");
6250
+ fs37.writeFileSync(outputPath, lines.join("\n"), "utf8");
5847
6251
  const succeeded = results.filter((r) => r.status === "success").length;
5848
6252
  console.log(`
5849
6253
  ${"=".repeat(50)}`);
@@ -5865,7 +6269,9 @@ function sleep5(ms) {
5865
6269
 
5866
6270
  // src/commands/doc/index.ts
5867
6271
  function registerDocCommands(program2) {
5868
- const doc = program2.command("doc").description("Document operations: convert PDFs, images, Office docs to Markdown via Gemini");
6272
+ const doc = program2.command("doc").description(
6273
+ "Document-to-Markdown: convert PDFs, images, DOCX, PPTX, and other docs to Markdown via Gemini"
6274
+ );
5869
6275
  doc.command("convert").description("Convert documents to Markdown using Gemini Files API").requiredOption(
5870
6276
  "--input <files...>",
5871
6277
  "Input file(s) to convert",
@@ -5889,12 +6295,12 @@ function registerDocCommands(program2) {
5889
6295
 
5890
6296
  // src/commands/media/index.ts
5891
6297
  init_errors();
5892
- import fs39 from "fs";
5893
- import path44 from "path";
6298
+ import fs40 from "fs";
6299
+ import path45 from "path";
5894
6300
 
5895
6301
  // src/commands/media/batch.ts
5896
- import fs37 from "fs";
5897
- import path42 from "path";
6302
+ import fs38 from "fs";
6303
+ import path43 from "path";
5898
6304
 
5899
6305
  // src/commands/media/detect-type.ts
5900
6306
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -6090,10 +6496,10 @@ async function batchOptimize(opts) {
6090
6496
  verbose,
6091
6497
  logger
6092
6498
  } = opts;
6093
- fs37.mkdirSync(outputDir, { recursive: true });
6094
- const entries = fs37.readdirSync(inputDir);
6499
+ fs38.mkdirSync(outputDir, { recursive: true });
6500
+ const entries = fs38.readdirSync(inputDir);
6095
6501
  const mediaFiles = entries.filter((f) => {
6096
- const ext = path42.extname(f).toLowerCase();
6502
+ const ext = path43.extname(f).toLowerCase();
6097
6503
  return ALL_EXTS.has(ext);
6098
6504
  });
6099
6505
  if (mediaFiles.length === 0) {
@@ -6104,9 +6510,9 @@ async function batchOptimize(opts) {
6104
6510
  let succeeded = 0;
6105
6511
  const errors = [];
6106
6512
  for (const file of mediaFiles) {
6107
- const inputPath = path42.join(inputDir, file);
6108
- const outputPath = path42.join(outputDir, file);
6109
- const ext = path42.extname(file).toLowerCase();
6513
+ const inputPath = path43.join(inputDir, file);
6514
+ const outputPath = path43.join(outputDir, file);
6515
+ const ext = path43.extname(file).toLowerCase();
6110
6516
  const kind = extToKind(ext);
6111
6517
  logger?.debug(`Processing: ${file}`);
6112
6518
  try {
@@ -6143,8 +6549,8 @@ async function batchOptimize(opts) {
6143
6549
 
6144
6550
  // src/commands/media/split-video.ts
6145
6551
  init_errors();
6146
- import fs38 from "fs";
6147
- import path43 from "path";
6552
+ import fs39 from "fs";
6553
+ import path44 from "path";
6148
6554
  async function splitVideo(opts) {
6149
6555
  await assertFfmpeg();
6150
6556
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -6155,10 +6561,10 @@ async function splitVideo(opts) {
6155
6561
  logger?.info("Video is short enough \u2014 no splitting needed");
6156
6562
  return [input];
6157
6563
  }
6158
- fs38.mkdirSync(outputDir, { recursive: true });
6159
- const ext = path43.extname(input) || ".mp4";
6160
- const baseName = path43.basename(input, ext);
6161
- const outputPattern = path43.join(outputDir, `${baseName}_chunk_%03d${ext}`);
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}`);
6162
6568
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
6163
6569
  const args = [
6164
6570
  "-i",
@@ -6177,8 +6583,8 @@ async function splitVideo(opts) {
6177
6583
  await runFfmpeg(args, { verbose, logger });
6178
6584
  const files = [];
6179
6585
  for (let i = 0; i < numChunks + 1; i++) {
6180
- const fname = path43.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6181
- if (fs38.existsSync(fname)) files.push(fname);
6586
+ const fname = path44.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6587
+ if (fs39.existsSync(fname)) files.push(fname);
6182
6588
  }
6183
6589
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
6184
6590
  return files;
@@ -6187,14 +6593,14 @@ async function splitVideo(opts) {
6187
6593
  // src/commands/media/index.ts
6188
6594
  function registerMediaCommands(program2) {
6189
6595
  const media = program2.command("media").description(
6190
- "Media optimization: compress video/audio/images, split long videos, batch process"
6596
+ "Media processing via ffmpeg/ImageMagick: compress video/audio/images, split long videos, batch optimize directories"
6191
6597
  );
6192
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(
6193
6599
  async (opts) => {
6194
6600
  const logger = createLogger({ verbose: opts.verbose ?? false });
6195
- if (!fs39.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6196
- fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6197
- const ext = path44.extname(opts.input).toLowerCase();
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();
6198
6604
  const kind = extToKind(ext);
6199
6605
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
6200
6606
  const quality = Number.parseInt(opts.quality, 10);
@@ -6234,7 +6640,7 @@ function registerMediaCommands(program2) {
6234
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(
6235
6641
  async (opts) => {
6236
6642
  const logger = createLogger({ verbose: opts.verbose ?? false });
6237
- if (!fs39.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6643
+ if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6238
6644
  const chunks = await splitVideo({
6239
6645
  input: opts.input,
6240
6646
  outputDir: opts.outputDir,
@@ -6250,7 +6656,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
6250
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(
6251
6657
  async (opts) => {
6252
6658
  const logger = createLogger({ verbose: opts.verbose ?? false });
6253
- if (!fs39.existsSync(opts.inputDir))
6659
+ if (!fs40.existsSync(opts.inputDir))
6254
6660
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
6255
6661
  const result = await batchOptimize({
6256
6662
  inputDir: opts.inputDir,
@@ -6280,6 +6686,7 @@ function registerCommands(program2) {
6280
6686
  registerOpenRouterCommands(program2);
6281
6687
  registerLeonardoCommands(program2);
6282
6688
  registerBytePlusCommands(program2);
6689
+ registerCloudflareCommands(program2);
6283
6690
  registerElevenLabsCommands(program2);
6284
6691
  registerMediaCommands(program2);
6285
6692
  registerDocCommands(program2);
@@ -6294,7 +6701,7 @@ loadEnv();
6294
6701
  var require2 = createRequire(import.meta.url);
6295
6702
  var pkg = require2("../package.json");
6296
6703
  var program = new Command().name("multix").description(
6297
- "AI multimodal CLI \u2014 Gemini \xB7 MiniMax \xB7 OpenRouter \xB7 ffmpeg \xB7 ImageMagick \xB7 doc-to-md"
6704
+ "AI multimodal CLI \u2014 image/video/audio/3D generation, editing, transcription, and media processing via OpenAI, Gemini, MiniMax, OpenRouter, Leonardo, BytePlus, ElevenLabs, ffmpeg, and ImageMagick"
6298
6705
  ).version(pkg.version);
6299
6706
  registerCommands(program);
6300
6707
  program.parseAsync(process.argv).catch((err2) => {