@mrgoonie/multix 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,
@@ -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),
@@ -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++) {
@@ -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++;
@@ -3821,8 +4187,8 @@ function sleep4(ms) {
3821
4187
 
3822
4188
  // src/providers/minimax/generators/music.ts
3823
4189
  init_http_client();
3824
- import fs24 from "fs";
3825
- import path29 from "path";
4190
+ import fs25 from "fs";
4191
+ import path30 from "path";
3826
4192
  async function generateMinimaxMusic(opts) {
3827
4193
  const {
3828
4194
  apiKey,
@@ -3867,14 +4233,14 @@ async function generateMinimaxMusic(opts) {
3867
4233
  }
3868
4234
  const durationMs = resp.extra_info?.music_duration ?? 0;
3869
4235
  const outDir = getOutputDir();
3870
- const dest = path29.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
4236
+ const dest = path30.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3871
4237
  try {
3872
4238
  if (audioData.startsWith("http")) {
3873
4239
  const bytes = await fetchBytes(audioData);
3874
- fs24.writeFileSync(dest, bytes);
4240
+ fs25.writeFileSync(dest, bytes);
3875
4241
  } else {
3876
4242
  const audioBytes = Buffer.from(audioData, "hex");
3877
- fs24.writeFileSync(dest, audioBytes);
4243
+ fs25.writeFileSync(dest, audioBytes);
3878
4244
  }
3879
4245
  } catch (e) {
3880
4246
  return {
@@ -3885,8 +4251,8 @@ async function generateMinimaxMusic(opts) {
3885
4251
  const durSec = durationMs / 1e3;
3886
4252
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
3887
4253
  if (output) {
3888
- fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3889
- fs24.copyFileSync(dest, output);
4254
+ fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4255
+ fs25.copyFileSync(dest, output);
3890
4256
  }
3891
4257
  return { status: "success", generatedAudio: dest, durationMs, model };
3892
4258
  }
@@ -3960,8 +4326,8 @@ Generated music: ${result.generatedAudio}`);
3960
4326
  init_errors();
3961
4327
 
3962
4328
  // src/providers/minimax/generators/speech.ts
3963
- import fs25 from "fs";
3964
- import path30 from "path";
4329
+ import fs26 from "fs";
4330
+ import path31 from "path";
3965
4331
  async function generateMinimaxSpeech(opts) {
3966
4332
  const {
3967
4333
  apiKey,
@@ -4008,12 +4374,12 @@ async function generateMinimaxSpeech(opts) {
4008
4374
  const audioBytes = Buffer.from(audioHex, "hex");
4009
4375
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
4010
4376
  const outDir = getOutputDir();
4011
- const dest = path30.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4012
- fs25.writeFileSync(dest, audioBytes);
4377
+ const dest = path31.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4378
+ fs26.writeFileSync(dest, audioBytes);
4013
4379
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
4014
4380
  if (output) {
4015
- fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4016
- fs25.copyFileSync(dest, output);
4381
+ fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4382
+ fs26.copyFileSync(dest, output);
4017
4383
  }
4018
4384
  return { status: "success", generatedAudio: dest, model };
4019
4385
  }
@@ -4060,8 +4426,8 @@ Generated audio: ${result.generatedAudio}`);
4060
4426
  init_errors();
4061
4427
 
4062
4428
  // src/providers/minimax/generators/video.ts
4063
- import fs26 from "fs";
4064
- import path31 from "path";
4429
+ import fs27 from "fs";
4430
+ import path32 from "path";
4065
4431
  async function generateMinimaxVideo(opts) {
4066
4432
  const {
4067
4433
  apiKey,
@@ -4107,18 +4473,18 @@ async function generateMinimaxVideo(opts) {
4107
4473
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
4108
4474
  }
4109
4475
  const outDir = getOutputDir();
4110
- const dest = path31.join(outDir, `minimax_video_${Date.now()}.mp4`);
4476
+ const dest = path32.join(outDir, `minimax_video_${Date.now()}.mp4`);
4111
4477
  try {
4112
4478
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
4113
4479
  } catch (e) {
4114
4480
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
4115
4481
  }
4116
4482
  const elapsed = (Date.now() - start) / 1e3;
4117
- const stat = fs26.statSync(dest);
4483
+ const stat = fs27.statSync(dest);
4118
4484
  const fileSizeMb = stat.size / (1024 * 1024);
4119
4485
  if (output) {
4120
- fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4121
- fs26.copyFileSync(dest, output);
4486
+ fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4487
+ fs27.copyFileSync(dest, output);
4122
4488
  }
4123
4489
  await maybeDownloadThumb(pollResult, dest, {
4124
4490
  skip: thumb === false,
@@ -4181,8 +4547,8 @@ Generated video: ${result.generatedVideo}`);
4181
4547
  // src/providers/minimax/generators/image.ts
4182
4548
  init_errors();
4183
4549
  init_http_client();
4184
- import fs27 from "fs";
4185
- import path32 from "path";
4550
+ import fs28 from "fs";
4551
+ import path33 from "path";
4186
4552
  async function generateMinimaxImage(opts) {
4187
4553
  const {
4188
4554
  apiKey,
@@ -4220,8 +4586,8 @@ async function generateMinimaxImage(opts) {
4220
4586
  try {
4221
4587
  const bytes = await fetchBytes(url);
4222
4588
  const fname = `minimax_image_${Date.now()}_${i}.png`;
4223
- const dest = path32.join(outDir, fname);
4224
- fs27.writeFileSync(dest, bytes);
4589
+ const dest = path33.join(outDir, fname);
4590
+ fs28.writeFileSync(dest, bytes);
4225
4591
  savedFiles.push(dest);
4226
4592
  logger?.success(`Saved: ${dest}`);
4227
4593
  } catch (e) {
@@ -4232,8 +4598,8 @@ async function generateMinimaxImage(opts) {
4232
4598
  }
4233
4599
  }
4234
4600
  if (output && savedFiles[0]) {
4235
- fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4236
- fs27.copyFileSync(savedFiles[0], output);
4601
+ fs28.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
4602
+ fs28.copyFileSync(savedFiles[0], output);
4237
4603
  }
4238
4604
  return { status: "success", generatedImages: savedFiles, model };
4239
4605
  }
@@ -4269,8 +4635,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
4269
4635
  // src/providers/minimax/commands/image-to-image.ts
4270
4636
  init_errors();
4271
4637
  init_http_client();
4272
- import fs28 from "fs";
4273
- import path33 from "path";
4638
+ import fs29 from "fs";
4639
+ import path34 from "path";
4274
4640
  function registerMinimaxImageToImageCommand(parent) {
4275
4641
  parent.command("image-to-image").alias("i2i").description(
4276
4642
  "MiniMax character/subject preservation. CAVEAT: NOT free-form image editing \u2014 the --ref image is used only as a character/face reference for a NEW scene described by --prompt."
@@ -4325,8 +4691,8 @@ function registerMinimaxImageToImageCommand(parent) {
4325
4691
  if (!url) continue;
4326
4692
  try {
4327
4693
  const bytes = await fetchBytes(url);
4328
- const dest = path33.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4329
- fs28.writeFileSync(dest, bytes);
4694
+ const dest = path34.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4695
+ fs29.writeFileSync(dest, bytes);
4330
4696
  saved.push(dest);
4331
4697
  logger.success(`Saved: ${dest}`);
4332
4698
  } catch (e) {
@@ -4337,8 +4703,8 @@ function registerMinimaxImageToImageCommand(parent) {
4337
4703
  }
4338
4704
  }
4339
4705
  if (opts.output && saved[0]) {
4340
- fs28.mkdirSync(path33.dirname(path33.resolve(opts.output)), { recursive: true });
4341
- fs28.copyFileSync(saved[0], opts.output);
4706
+ fs29.mkdirSync(path34.dirname(path34.resolve(opts.output)), { recursive: true });
4707
+ fs29.copyFileSync(saved[0], opts.output);
4342
4708
  logger.success(`Copied to: ${opts.output}`);
4343
4709
  }
4344
4710
  console.log(`
@@ -4472,12 +4838,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
4472
4838
 
4473
4839
  // src/providers/openai/openai-audio.ts
4474
4840
  init_http_client();
4475
- import fs30 from "fs";
4476
- import path35 from "path";
4841
+ import fs31 from "fs";
4842
+ import path36 from "path";
4477
4843
 
4478
4844
  // src/providers/openai/client.ts
4479
- import fs29 from "fs";
4480
- import path34 from "path";
4845
+ import fs30 from "fs";
4846
+ import path35 from "path";
4481
4847
  init_errors();
4482
4848
  init_http_client();
4483
4849
  var DEFAULT_TIMEOUT_MS3 = 24e4;
@@ -4544,10 +4910,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
4544
4910
  return Buffer.from(await res.arrayBuffer());
4545
4911
  }
4546
4912
  function fileFromPath(filePath) {
4547
- const abs = path34.resolve(filePath);
4548
- if (!fs29.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4549
- const bytes = fs29.readFileSync(abs);
4550
- 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));
4551
4917
  }
4552
4918
 
4553
4919
  // src/providers/openai/openai-audio.ts
@@ -4586,7 +4952,7 @@ async function resolveAudioReferenceDataUrl(ref) {
4586
4952
  }
4587
4953
  function audioMimeFromReference(ref) {
4588
4954
  const pathname = new URL(ref).pathname;
4589
- const ext = path35.extname(pathname).toLowerCase();
4955
+ const ext = path36.extname(pathname).toLowerCase();
4590
4956
  switch (ext) {
4591
4957
  case ".mp3":
4592
4958
  return "audio/mpeg";
@@ -4605,12 +4971,12 @@ function audioMimeFromReference(ref) {
4605
4971
  async function generateOpenAISpeech(opts) {
4606
4972
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
4607
4973
  const ext = extFromAudioFormat(opts.outputFormat);
4608
- const dest = path35.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4609
- fs30.writeFileSync(dest, bytes);
4974
+ const dest = path36.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4975
+ fs31.writeFileSync(dest, bytes);
4610
4976
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
4611
4977
  if (opts.output) {
4612
- fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4613
- fs30.copyFileSync(dest, opts.output);
4978
+ fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
4979
+ fs31.copyFileSync(dest, opts.output);
4614
4980
  }
4615
4981
  return dest;
4616
4982
  }
@@ -4632,9 +4998,9 @@ async function transcribeOpenAIAudio(opts) {
4632
4998
  );
4633
4999
  const text = typeof data.text === "string" ? data.text : void 0;
4634
5000
  const ext = opts.format === "text" ? "txt" : "json";
4635
- const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
4636
- fs30.mkdirSync(path35.dirname(dest), { recursive: true });
4637
- 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));
4638
5004
  opts.logger?.success(`Saved transcript: ${dest}`);
4639
5005
  return { path: dest, text };
4640
5006
  }
@@ -4661,9 +5027,9 @@ Generated audio: ${dest}`);
4661
5027
  }
4662
5028
 
4663
5029
  // src/providers/openai/codex-image-driver.ts
4664
- import fs31 from "fs";
5030
+ import fs32 from "fs";
4665
5031
  import os2 from "os";
4666
- import path36 from "path";
5032
+ import path37 from "path";
4667
5033
  import { execa } from "execa";
4668
5034
  init_errors();
4669
5035
  var defaultRunner = async (cmd, args, opts) => {
@@ -4712,12 +5078,12 @@ function sanitizeCodexEnv() {
4712
5078
  async function runCodexImageGeneration(opts) {
4713
5079
  const runner = opts.runner ?? defaultRunner;
4714
5080
  const ext = extFromImageFormat(opts.outputFormat);
4715
- const dest = opts.output ? path36.resolve(opts.output) : path36.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
4716
- fs31.mkdirSync(path36.dirname(dest), { recursive: true });
4717
- const tmp = fs31.mkdtempSync(path36.join(os2.tmpdir(), "multix-codex-image-"));
4718
- const schema = path36.join(tmp, "schema.json");
4719
- const result = path36.join(tmp, "result.json");
4720
- 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(
4721
5087
  schema,
4722
5088
  JSON.stringify({
4723
5089
  type: "object",
@@ -4747,7 +5113,7 @@ async function runCodexImageGeneration(opts) {
4747
5113
  if (res.exitCode !== 0) {
4748
5114
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
4749
5115
  }
4750
- if (!fs31.existsSync(dest) || fs31.statSync(dest).size === 0) {
5116
+ if (!fs32.existsSync(dest) || fs32.statSync(dest).size === 0) {
4751
5117
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
4752
5118
  }
4753
5119
  return { path: dest };
@@ -4765,8 +5131,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
4765
5131
  // src/providers/openai/openai-image.ts
4766
5132
  init_errors();
4767
5133
  init_http_client();
4768
- import fs32 from "fs";
4769
- import path37 from "path";
5134
+ import fs33 from "fs";
5135
+ import path38 from "path";
4770
5136
  function buildImageGenerationBody(opts) {
4771
5137
  return {
4772
5138
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -4799,14 +5165,14 @@ function saveOpenAIImages(opts) {
4799
5165
  for (let i = 0; i < items.length; i++) {
4800
5166
  const b64 = items[i]?.b64_json;
4801
5167
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
4802
- const dest = path37.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
4803
- 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"));
4804
5170
  saved.push(dest);
4805
5171
  opts.logger?.success(`Saved: ${dest}`);
4806
5172
  }
4807
5173
  if (opts.output && saved[0]) {
4808
- fs32.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
4809
- fs32.copyFileSync(saved[0], opts.output);
5174
+ fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5175
+ fs33.copyFileSync(saved[0], opts.output);
4810
5176
  opts.logger?.success(`Copied to: ${opts.output}`);
4811
5177
  }
4812
5178
  return saved;
@@ -4815,7 +5181,7 @@ async function resolveImageRefBytes(ref) {
4815
5181
  if (isHttpUrl(ref)) {
4816
5182
  const bytes2 = await fetchBytes(ref);
4817
5183
  const pathname = new URL(ref).pathname;
4818
- const filename = path37.basename(pathname) || "reference.png";
5184
+ const filename = path38.basename(pathname) || "reference.png";
4819
5185
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
4820
5186
  }
4821
5187
  const file = fileFromPath(ref);
@@ -4860,7 +5226,7 @@ function sniffImageMime2(buf, name) {
4860
5226
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
4861
5227
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
4862
5228
  return "image/webp";
4863
- const ext = path37.extname(name).toLowerCase();
5229
+ const ext = path38.extname(name).toLowerCase();
4864
5230
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
4865
5231
  if (ext === ".webp") return "image/webp";
4866
5232
  return "image/png";
@@ -4988,8 +5354,8 @@ function registerOpenAICommands(program2) {
4988
5354
  }
4989
5355
 
4990
5356
  // src/providers/openrouter/client.ts
4991
- import fs33 from "fs";
4992
- import path38 from "path";
5357
+ import fs34 from "fs";
5358
+ import path39 from "path";
4993
5359
  init_errors();
4994
5360
  init_http_client();
4995
5361
 
@@ -5154,8 +5520,8 @@ async function generateOpenRouterImage(opts) {
5154
5520
  if (!url) continue;
5155
5521
  try {
5156
5522
  const bytes = await fetchBytes(url);
5157
- const dest = path38.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5158
- fs33.writeFileSync(dest, bytes);
5523
+ const dest = path39.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5524
+ fs34.writeFileSync(dest, bytes);
5159
5525
  savedFiles.push(dest);
5160
5526
  logger?.success(`Saved: ${dest}`);
5161
5527
  } catch (e) {
@@ -5166,8 +5532,8 @@ async function generateOpenRouterImage(opts) {
5166
5532
  }
5167
5533
  }
5168
5534
  if (output && savedFiles[0]) {
5169
- fs33.mkdirSync(path38.dirname(path38.resolve(output)), { recursive: true });
5170
- 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]));
5171
5537
  }
5172
5538
  return { status: "success", generatedImages: savedFiles, model: usedModel };
5173
5539
  }
@@ -5204,8 +5570,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
5204
5570
  }
5205
5571
 
5206
5572
  // src/providers/openrouter/commands/image-to-image.ts
5207
- import fs34 from "fs";
5208
- import path39 from "path";
5573
+ import fs35 from "fs";
5574
+ import path40 from "path";
5209
5575
  init_errors();
5210
5576
  init_http_client();
5211
5577
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
@@ -5297,14 +5663,14 @@ async function runImageToImage(opts) {
5297
5663
  const url = parsed.urls[i];
5298
5664
  if (!url) continue;
5299
5665
  const bytes = await fetchBytes(url);
5300
- const dest = path39.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5301
- fs34.writeFileSync(dest, bytes);
5666
+ const dest = path40.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5667
+ fs35.writeFileSync(dest, bytes);
5302
5668
  saved.push(dest);
5303
5669
  opts.logger.success(`Saved: ${dest}`);
5304
5670
  }
5305
5671
  if (opts.output && saved[0]) {
5306
- fs34.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5307
- fs34.copyFileSync(saved[0], opts.output);
5672
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5673
+ fs35.copyFileSync(saved[0], opts.output);
5308
5674
  opts.logger.success(`Copied to: ${opts.output}`);
5309
5675
  }
5310
5676
  return saved;
@@ -5350,13 +5716,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
5350
5716
  }
5351
5717
 
5352
5718
  // src/providers/openrouter/commands/video-status.ts
5353
- import fs35 from "fs";
5354
- import path40 from "path";
5719
+ import fs36 from "fs";
5720
+ import path41 from "path";
5355
5721
  async function downloadOpenRouterVideo(opts) {
5356
5722
  const { jobId, logger, source } = opts;
5357
5723
  const apiKey = requireOpenRouterKey();
5358
5724
  const url = videoContentUrl(jobId);
5359
- const dest = path40.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5725
+ const dest = path41.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5360
5726
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
5361
5727
  if (!r.ok) {
5362
5728
  logger.error(`Download HTTP ${r.status}`);
@@ -5366,14 +5732,14 @@ async function downloadOpenRouterVideo(opts) {
5366
5732
  logger.error("Empty response body");
5367
5733
  process.exit(1);
5368
5734
  }
5369
- fs35.mkdirSync(path40.dirname(dest), { recursive: true });
5370
- const ws = fs35.createWriteStream(dest);
5735
+ fs36.mkdirSync(path41.dirname(dest), { recursive: true });
5736
+ const ws = fs36.createWriteStream(dest);
5371
5737
  const { pipeline: pipeline2 } = await import("stream/promises");
5372
5738
  await pipeline2(r.body, ws);
5373
5739
  logger.success(`Saved ${dest}`);
5374
5740
  if (opts.output) {
5375
- fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5376
- fs35.copyFileSync(dest, opts.output);
5741
+ fs36.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
5742
+ fs36.copyFileSync(dest, opts.output);
5377
5743
  logger.success(`Copied to ${opts.output}`);
5378
5744
  }
5379
5745
  await maybeDownloadThumb(source, dest, {
@@ -5664,6 +6030,16 @@ function resolveProviderKey(p) {
5664
6030
  }
5665
6031
  return {};
5666
6032
  }
6033
+ function cloudflareReadiness() {
6034
+ const accountId = resolveKey("CLOUDFLARE_ACCOUNT_ID")?.trim();
6035
+ const apiToken = resolveKey("CLOUDFLARE_API_TOKEN")?.trim();
6036
+ const gatewayId = resolveKey("CLOUDFLARE_AI_GATEWAY_ID")?.trim();
6037
+ const replicateToken = resolveKey("REPLICATE_API_TOKEN")?.trim();
6038
+ return {
6039
+ native: Boolean(accountId && apiToken),
6040
+ video: Boolean(accountId && apiToken && gatewayId && replicateToken)
6041
+ };
6042
+ }
5667
6043
  async function checkCodexImageReadiness(hasAnyProviderKey, verbose = false, checker = checkCodexAuthenticated) {
5668
6044
  if (hasAnyProviderKey && !verbose) return false;
5669
6045
  return checker();
@@ -5704,6 +6080,20 @@ function registerCheckCommand(program2) {
5704
6080
  logger.warn(`${p.envPrimary} not set (${p.name} features unavailable)`);
5705
6081
  }
5706
6082
  }
6083
+ const cloudflare = cloudflareReadiness();
6084
+ if (cloudflare.native) {
6085
+ anyKey = true;
6086
+ logger.success("Cloudflare Workers AI credentials configured (image and speech)");
6087
+ if (cloudflare.video) {
6088
+ logger.success("Cloudflare AI Gateway video credentials configured");
6089
+ } else {
6090
+ logger.info("Cloudflare video needs CLOUDFLARE_AI_GATEWAY_ID and REPLICATE_API_TOKEN");
6091
+ }
6092
+ } else {
6093
+ logger.info(
6094
+ "Cloudflare media needs CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN (optional)"
6095
+ );
6096
+ }
5707
6097
  const shouldCheckCodex = !anyKey || opts.verbose;
5708
6098
  let codexReady = false;
5709
6099
  if (shouldCheckCodex) {
@@ -5760,8 +6150,8 @@ function registerCheckCommand(program2) {
5760
6150
 
5761
6151
  // src/commands/doc/convert.ts
5762
6152
  init_errors();
5763
- import fs36 from "fs";
5764
- import path41 from "path";
6153
+ import fs37 from "fs";
6154
+ import path42 from "path";
5765
6155
 
5766
6156
  // src/commands/doc/default-prompt.ts
5767
6157
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -5782,18 +6172,18 @@ async function convertDocuments(opts) {
5782
6172
  const model = opts.model ?? DOC_MODEL_DEFAULT;
5783
6173
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
5784
6174
  for (const f of inputs) {
5785
- if (!fs36.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
6175
+ if (!fs37.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
5786
6176
  }
5787
6177
  let outputPath;
5788
6178
  if (output) {
5789
- outputPath = path41.resolve(output);
6179
+ outputPath = path42.resolve(output);
5790
6180
  } else if (autoName && inputs.length === 1 && inputs[0]) {
5791
- const base = path41.basename(inputs[0], path41.extname(inputs[0]));
5792
- 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`);
5793
6183
  } else {
5794
- outputPath = path41.join(getOutputDir(), "document-extraction.md");
6184
+ outputPath = path42.join(getOutputDir(), "document-extraction.md");
5795
6185
  }
5796
- fs36.mkdirSync(path41.dirname(outputPath), { recursive: true });
6186
+ fs37.mkdirSync(path42.dirname(outputPath), { recursive: true });
5797
6187
  const results = [];
5798
6188
  for (let i = 0; i < inputs.length; i++) {
5799
6189
  const filePath = inputs[i];
@@ -5844,7 +6234,7 @@ async function convertDocuments(opts) {
5844
6234
  `);
5845
6235
  for (const r of results) {
5846
6236
  lines.push(`
5847
- ## ${path41.basename(r.file)}
6237
+ ## ${path42.basename(r.file)}
5848
6238
  `);
5849
6239
  if (r.status === "success" && r.markdown) {
5850
6240
  lines.push(`${r.markdown}
@@ -5857,7 +6247,7 @@ async function convertDocuments(opts) {
5857
6247
  }
5858
6248
  lines.push("\n---\n");
5859
6249
  }
5860
- fs36.writeFileSync(outputPath, lines.join("\n"), "utf8");
6250
+ fs37.writeFileSync(outputPath, lines.join("\n"), "utf8");
5861
6251
  const succeeded = results.filter((r) => r.status === "success").length;
5862
6252
  console.log(`
5863
6253
  ${"=".repeat(50)}`);
@@ -5905,12 +6295,12 @@ function registerDocCommands(program2) {
5905
6295
 
5906
6296
  // src/commands/media/index.ts
5907
6297
  init_errors();
5908
- import fs39 from "fs";
5909
- import path44 from "path";
6298
+ import fs40 from "fs";
6299
+ import path45 from "path";
5910
6300
 
5911
6301
  // src/commands/media/batch.ts
5912
- import fs37 from "fs";
5913
- import path42 from "path";
6302
+ import fs38 from "fs";
6303
+ import path43 from "path";
5914
6304
 
5915
6305
  // src/commands/media/detect-type.ts
5916
6306
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -6106,10 +6496,10 @@ async function batchOptimize(opts) {
6106
6496
  verbose,
6107
6497
  logger
6108
6498
  } = opts;
6109
- fs37.mkdirSync(outputDir, { recursive: true });
6110
- const entries = fs37.readdirSync(inputDir);
6499
+ fs38.mkdirSync(outputDir, { recursive: true });
6500
+ const entries = fs38.readdirSync(inputDir);
6111
6501
  const mediaFiles = entries.filter((f) => {
6112
- const ext = path42.extname(f).toLowerCase();
6502
+ const ext = path43.extname(f).toLowerCase();
6113
6503
  return ALL_EXTS.has(ext);
6114
6504
  });
6115
6505
  if (mediaFiles.length === 0) {
@@ -6120,9 +6510,9 @@ async function batchOptimize(opts) {
6120
6510
  let succeeded = 0;
6121
6511
  const errors = [];
6122
6512
  for (const file of mediaFiles) {
6123
- const inputPath = path42.join(inputDir, file);
6124
- const outputPath = path42.join(outputDir, file);
6125
- 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();
6126
6516
  const kind = extToKind(ext);
6127
6517
  logger?.debug(`Processing: ${file}`);
6128
6518
  try {
@@ -6159,8 +6549,8 @@ async function batchOptimize(opts) {
6159
6549
 
6160
6550
  // src/commands/media/split-video.ts
6161
6551
  init_errors();
6162
- import fs38 from "fs";
6163
- import path43 from "path";
6552
+ import fs39 from "fs";
6553
+ import path44 from "path";
6164
6554
  async function splitVideo(opts) {
6165
6555
  await assertFfmpeg();
6166
6556
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -6171,10 +6561,10 @@ async function splitVideo(opts) {
6171
6561
  logger?.info("Video is short enough \u2014 no splitting needed");
6172
6562
  return [input];
6173
6563
  }
6174
- fs38.mkdirSync(outputDir, { recursive: true });
6175
- const ext = path43.extname(input) || ".mp4";
6176
- const baseName = path43.basename(input, ext);
6177
- 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}`);
6178
6568
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
6179
6569
  const args = [
6180
6570
  "-i",
@@ -6193,8 +6583,8 @@ async function splitVideo(opts) {
6193
6583
  await runFfmpeg(args, { verbose, logger });
6194
6584
  const files = [];
6195
6585
  for (let i = 0; i < numChunks + 1; i++) {
6196
- const fname = path43.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6197
- 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);
6198
6588
  }
6199
6589
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
6200
6590
  return files;
@@ -6208,9 +6598,9 @@ function registerMediaCommands(program2) {
6208
6598
  media.command("optimize").description("Optimize a single media file (video, audio, or image)").requiredOption("--input <file>", "Input media file").requiredOption("--output <file>", "Output file path").option("--target-size <MB>", "Target file size in MB").option("--quality <n>", "Quality: video CRF (0-51) or image (1-100)", "85").option("--max-width <px>", "Max image/video width in pixels", "1920").option("--bitrate <rate>", "Audio bitrate (e.g. 64k)", "64k").option("--resolution <WxH>", "Force video resolution (e.g. 1920x1080)").option("-v, --verbose", "Verbose logging").action(
6209
6599
  async (opts) => {
6210
6600
  const logger = createLogger({ verbose: opts.verbose ?? false });
6211
- if (!fs39.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6212
- fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6213
- 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();
6214
6604
  const kind = extToKind(ext);
6215
6605
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
6216
6606
  const quality = Number.parseInt(opts.quality, 10);
@@ -6250,7 +6640,7 @@ function registerMediaCommands(program2) {
6250
6640
  media.command("split").description("Split a long video into fixed-duration chunks").requiredOption("--input <file>", "Input video file").option("--output-dir <dir>", "Output directory for chunks", "./chunks").option("--chunk-duration <sec>", "Chunk duration in seconds", "3600").option("-v, --verbose", "Verbose logging").action(
6251
6641
  async (opts) => {
6252
6642
  const logger = createLogger({ verbose: opts.verbose ?? false });
6253
- if (!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}`);
6254
6644
  const chunks = await splitVideo({
6255
6645
  input: opts.input,
6256
6646
  outputDir: opts.outputDir,
@@ -6266,7 +6656,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
6266
6656
  media.command("batch").description("Batch optimize all media files in a directory").requiredOption("--input-dir <dir>", "Input directory").requiredOption("--output-dir <dir>", "Output directory").option("--quality <n>", "Quality setting", "85").option("--max-width <px>", "Max image/video width", "1920").option("--bitrate <rate>", "Audio bitrate", "64k").option("-v, --verbose", "Verbose logging").action(
6267
6657
  async (opts) => {
6268
6658
  const logger = createLogger({ verbose: opts.verbose ?? false });
6269
- if (!fs39.existsSync(opts.inputDir))
6659
+ if (!fs40.existsSync(opts.inputDir))
6270
6660
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
6271
6661
  const result = await batchOptimize({
6272
6662
  inputDir: opts.inputDir,
@@ -6296,6 +6686,7 @@ function registerCommands(program2) {
6296
6686
  registerOpenRouterCommands(program2);
6297
6687
  registerLeonardoCommands(program2);
6298
6688
  registerBytePlusCommands(program2);
6689
+ registerCloudflareCommands(program2);
6299
6690
  registerElevenLabsCommands(program2);
6300
6691
  registerMediaCommands(program2);
6301
6692
  registerDocCommands(program2);