@mrgoonie/multix 0.0.6 → 0.0.7

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
@@ -10,6 +10,14 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/core/errors.ts
13
+ var errors_exports = {};
14
+ __export(errors_exports, {
15
+ ConfigError: () => ConfigError,
16
+ HttpError: () => HttpError,
17
+ MultixError: () => MultixError,
18
+ ProviderError: () => ProviderError,
19
+ ValidationError: () => ValidationError
20
+ });
13
21
  var MultixError, ConfigError, ProviderError, ValidationError, HttpError;
14
22
  var init_errors = __esm({
15
23
  "src/core/errors.ts"() {
@@ -406,9 +414,9 @@ var BytePlusClient = class {
406
414
  }
407
415
  apiKey;
408
416
  baseUrl;
409
- async request(path36, opts = {}) {
417
+ async request(path41, opts = {}) {
410
418
  const base = this.baseUrl.replace(/\/$/, "");
411
- const url = new URL(base + path36);
419
+ const url = new URL(base + path41);
412
420
  if (opts.query) {
413
421
  for (const [k, v] of Object.entries(opts.query)) {
414
422
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -426,14 +434,14 @@ var BytePlusClient = class {
426
434
  timeoutMs: opts.timeoutMs ?? 12e4
427
435
  });
428
436
  }
429
- get(path36, query, logger) {
430
- return this.request(path36, { method: "GET", query, logger });
437
+ get(path41, query, logger) {
438
+ return this.request(path41, { method: "GET", query, logger });
431
439
  }
432
- post(path36, body, logger) {
433
- return this.request(path36, { method: "POST", body, logger });
440
+ post(path41, body, logger) {
441
+ return this.request(path41, { method: "POST", body, logger });
434
442
  }
435
- delete(path36, logger) {
436
- return this.request(path36, { method: "DELETE", logger });
443
+ delete(path41, logger) {
444
+ return this.request(path41, { method: "DELETE", logger });
437
445
  }
438
446
  };
439
447
  function createBytePlusClient() {
@@ -982,10 +990,10 @@ function toRef(kind, resolved, role) {
982
990
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
983
991
  }
984
992
  async function resolveRefSpec(kind, spec, logger) {
985
- const { path: path36, role } = parseRefSpec(spec);
986
- if (kind === "image") return toRef(kind, await resolveImageInput(path36, logger), role);
987
- if (kind === "video") return toRef(kind, await resolveVideoInput(path36, logger), role);
988
- return toRef(kind, await resolveAudioInput(path36, logger), role);
993
+ const { path: path41, role } = parseRefSpec(spec);
994
+ if (kind === "image") return toRef(kind, await resolveImageInput(path41, logger), role);
995
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path41, logger), role);
996
+ return toRef(kind, await resolveAudioInput(path41, logger), role);
989
997
  }
990
998
  async function resolveRefSpecs(specs, logger) {
991
999
  const out = [];
@@ -1243,18 +1251,780 @@ function registerBytePlusCommands(program2) {
1243
1251
  registerBytePlusStatusCommand(byteplus);
1244
1252
  }
1245
1253
 
1246
- // src/providers/gemini/commands/analyze.ts
1254
+ // src/providers/elevenlabs/client.ts
1255
+ import fs7 from "fs";
1256
+ import path12 from "path";
1247
1257
  init_errors();
1258
+ init_http_client();
1259
+
1260
+ // src/providers/elevenlabs/models.ts
1261
+ var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
1262
+ var ELEVENLABS_TTS_MODELS = [
1263
+ "eleven_v3",
1264
+ "eleven_multilingual_v2",
1265
+ "eleven_flash_v2_5",
1266
+ "eleven_flash_v2",
1267
+ "eleven_turbo_v2_5",
1268
+ "eleven_turbo_v2"
1269
+ ];
1270
+ var ELEVENLABS_STT_MODELS = ["scribe_v1", "scribe_v1_experimental"];
1271
+ var ELEVENLABS_OUTPUT_FORMATS = [
1272
+ "mp3_22050_32",
1273
+ "mp3_44100_32",
1274
+ "mp3_44100_64",
1275
+ "mp3_44100_96",
1276
+ "mp3_44100_128",
1277
+ "mp3_44100_192",
1278
+ "pcm_8000",
1279
+ "pcm_16000",
1280
+ "pcm_22050",
1281
+ "pcm_24000",
1282
+ "pcm_44100",
1283
+ "ulaw_8000"
1284
+ ];
1285
+ var ELEVENLABS_RECOMMENDED_VOICES = {
1286
+ alexandra: "kdmDKE6EkgrWrrykO9Qt",
1287
+ archer: "L0Dsvb3SLTyegXwtm47J",
1288
+ jessica_anne_bogart: "g6xIsTj2HwM6VR4iXFCw",
1289
+ hope: "OYTbf65OHHFELVut7v2H",
1290
+ eryn: "dj3G1R1ilKoFKhBnWOzG",
1291
+ stuart: "HDA9tsk27wYi3uq0fPcK",
1292
+ mark: "1SM7GgM6IMuvQlz2BwM3",
1293
+ angela: "PT4nqlKZfc06VW1BuClj",
1294
+ finn: "vBKc2FfBKJfcZNyEt1n6",
1295
+ cassidy: "56AoDkrOh6qfVPDXZ7Pt",
1296
+ grandpa_spuds_oxley: "NOpBlnGInO9m6vDvFkFC"
1297
+ };
1298
+ var TASK_DEFAULTS = {
1299
+ ttsModel: "eleven_multilingual_v2",
1300
+ ttsVoice: ELEVENLABS_RECOMMENDED_VOICES.alexandra,
1301
+ ttsFormat: "mp3_44100_128",
1302
+ sttModel: "scribe_v1",
1303
+ voiceChangerModel: "eleven_multilingual_sts_v2",
1304
+ sfxFormat: "mp3_44100_128",
1305
+ musicFormat: "mp3_44100_128"
1306
+ };
1307
+ function extFromFormat(fmt) {
1308
+ if (fmt.startsWith("mp3")) return "mp3";
1309
+ if (fmt.startsWith("pcm")) return "pcm";
1310
+ if (fmt.startsWith("ulaw")) return "ulaw";
1311
+ return "mp3";
1312
+ }
1313
+
1314
+ // src/providers/elevenlabs/client.ts
1315
+ var DEFAULT_TIMEOUT_MS2 = 12e4;
1316
+ function requireElevenLabsKey() {
1317
+ const key = resolveKey("ELEVENLABS_API_KEY");
1318
+ if (!key) {
1319
+ throw new ConfigError(
1320
+ "ELEVENLABS_API_KEY is not set. Get one at https://elevenlabs.io/app/settings/api-keys"
1321
+ );
1322
+ }
1323
+ return key;
1324
+ }
1325
+ function authHeaders(apiKey, extra) {
1326
+ return { "xi-api-key": apiKey, ...extra ?? {} };
1327
+ }
1328
+ function apiGet(endpoint, apiKey, query) {
1329
+ const url = new URL(`${ELEVENLABS_BASE_URL}/${endpoint.replace(/^\//, "")}`);
1330
+ if (query) {
1331
+ for (const [k, v] of Object.entries(query)) {
1332
+ if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
1333
+ }
1334
+ }
1335
+ return httpJson({ url: url.toString(), headers: authHeaders(apiKey) });
1336
+ }
1337
+ function apiPostJson(endpoint, payload, apiKey, opts = {}) {
1338
+ const url = new URL(`${ELEVENLABS_BASE_URL}/${endpoint.replace(/^\//, "")}`);
1339
+ if (opts.query) {
1340
+ for (const [k, v] of Object.entries(opts.query)) url.searchParams.set(k, v);
1341
+ }
1342
+ return httpJson({
1343
+ url: url.toString(),
1344
+ method: "POST",
1345
+ headers: authHeaders(apiKey, { "Content-Type": "application/json" }),
1346
+ body: payload,
1347
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2
1348
+ });
1349
+ }
1350
+ async function apiDelete(endpoint, apiKey) {
1351
+ const url = `${ELEVENLABS_BASE_URL}/${endpoint.replace(/^\//, "")}`;
1352
+ const res = await globalThis.fetch(url, {
1353
+ method: "DELETE",
1354
+ headers: authHeaders(apiKey),
1355
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
1356
+ });
1357
+ if (!res.ok) {
1358
+ const text = await res.text().catch(() => "");
1359
+ throw new HttpError(res.status, text.slice(0, 500), url);
1360
+ }
1361
+ }
1362
+ async function apiPostBinary(endpoint, body, apiKey, opts = {}) {
1363
+ const url = new URL(`${ELEVENLABS_BASE_URL}/${endpoint.replace(/^\//, "")}`);
1364
+ if (opts.query) {
1365
+ for (const [k, v] of Object.entries(opts.query)) url.searchParams.set(k, v);
1366
+ }
1367
+ const headers = authHeaders(apiKey);
1368
+ let init;
1369
+ if (body instanceof FormData) {
1370
+ init = {
1371
+ method: "POST",
1372
+ headers,
1373
+ body,
1374
+ signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
1375
+ };
1376
+ } else {
1377
+ init = {
1378
+ method: "POST",
1379
+ headers: { ...headers, "Content-Type": "application/json" },
1380
+ body: JSON.stringify(body),
1381
+ signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
1382
+ };
1383
+ }
1384
+ let res;
1385
+ try {
1386
+ res = await globalThis.fetch(url.toString(), init);
1387
+ } catch (cause) {
1388
+ const msg = cause instanceof Error ? cause.message : String(cause);
1389
+ throw new HttpError(0, msg, url.toString());
1390
+ }
1391
+ if (!res.ok) {
1392
+ const text = await res.text().catch(() => "");
1393
+ throw new HttpError(res.status, text.slice(0, 500), url.toString());
1394
+ }
1395
+ return Buffer.from(await res.arrayBuffer());
1396
+ }
1397
+ async function apiPostMultipart(endpoint, form, apiKey, opts = {}) {
1398
+ const url = `${ELEVENLABS_BASE_URL}/${endpoint.replace(/^\//, "")}`;
1399
+ let res;
1400
+ try {
1401
+ res = await globalThis.fetch(url, {
1402
+ method: "POST",
1403
+ headers: authHeaders(apiKey),
1404
+ body: form,
1405
+ signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
1406
+ });
1407
+ } catch (cause) {
1408
+ const msg = cause instanceof Error ? cause.message : String(cause);
1409
+ throw new HttpError(0, msg, url);
1410
+ }
1411
+ if (!res.ok) {
1412
+ const text = await res.text().catch(() => "");
1413
+ throw new HttpError(res.status, text.slice(0, 500), url);
1414
+ }
1415
+ return res.json();
1416
+ }
1417
+ 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);
1422
+ return new File([new Uint8Array(buf)], name);
1423
+ }
1424
+ async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
1425
+ const { intervalMs = 5e3, timeoutMs = 6e5, logger, label = "task" } = opts;
1426
+ const deadline = Date.now() + timeoutMs;
1427
+ let elapsed = 0;
1428
+ while (Date.now() < deadline) {
1429
+ const cur = await fetchStatus();
1430
+ if (isDone(cur)) return cur;
1431
+ if (isFailed(cur)) {
1432
+ throw new ProviderError(`${label} failed: ${JSON.stringify(cur)}`, "elevenlabs");
1433
+ }
1434
+ if (elapsed > 0 && elapsed % 3e4 < intervalMs) {
1435
+ logger?.debug(`Polling ${label}... ${Math.round(elapsed / 1e3)}s, status=${cur.status}`);
1436
+ }
1437
+ await new Promise((r) => setTimeout(r, intervalMs));
1438
+ elapsed += intervalMs;
1439
+ }
1440
+ throw new ProviderError(`${label} timed out after ${timeoutMs / 1e3}s`, "elevenlabs");
1441
+ }
1442
+ function saveBytes(opts) {
1443
+ const { bytes, task, ext, outputCopy, logger } = opts;
1444
+ const dir = getOutputDir();
1445
+ const dest = path12.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
1446
+ fs7.writeFileSync(dest, bytes);
1447
+ logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
1448
+ if (outputCopy) {
1449
+ fs7.mkdirSync(path12.dirname(path12.resolve(outputCopy)), { recursive: true });
1450
+ fs7.copyFileSync(dest, outputCopy);
1451
+ }
1452
+ return dest;
1453
+ }
1454
+
1455
+ // src/providers/elevenlabs/commands/account.ts
1456
+ function registerAccountCommands(parent) {
1457
+ parent.command("account").description("Show ElevenLabs account info, subscription tier, and usage").action(async () => {
1458
+ const apiKey = requireElevenLabsKey();
1459
+ const user = await apiGet("user", apiKey);
1460
+ const sub = user.subscription ?? {};
1461
+ console.log(`Tier: ${sub.tier ?? "?"}`);
1462
+ console.log(`Characters: ${sub.character_count ?? "?"} / ${sub.character_limit ?? "?"}`);
1463
+ if (sub.next_character_count_reset_unix) {
1464
+ console.log(
1465
+ `Resets: ${new Date(sub.next_character_count_reset_unix * 1e3).toISOString()}`
1466
+ );
1467
+ }
1468
+ console.log(
1469
+ `Voice slots: ${sub.voice_limit ?? "?"} (PVC: ${sub.professional_voice_limit ?? 0})`
1470
+ );
1471
+ if (user.first_name) console.log(`Name: ${user.first_name}`);
1472
+ });
1473
+ parent.command("models").description("List available ElevenLabs models").action(async () => {
1474
+ const apiKey = requireElevenLabsKey();
1475
+ const models = await apiGet("models", apiKey);
1476
+ for (const m of models) {
1477
+ const caps = [];
1478
+ if (m.can_do_text_to_speech) caps.push("tts");
1479
+ if (m.can_do_voice_conversion) caps.push("vc");
1480
+ const langs = m.languages?.length ? `${m.languages.length} langs` : "";
1481
+ console.log(`${m.model_id} ${m.name ?? ""} [${caps.join(",")}] ${langs}`);
1482
+ }
1483
+ console.log(`
1484
+ ${models.length} models`);
1485
+ });
1486
+ }
1487
+
1488
+ // src/providers/elevenlabs/commands/align.ts
1489
+ import fs8 from "fs";
1490
+ import path13 from "path";
1491
+ function registerAlignCommand(parent) {
1492
+ 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
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1494
+ const apiKey = requireElevenLabsKey();
1495
+ let text = opts.text;
1496
+ if (opts.textFile) text = fs8.readFileSync(path13.resolve(opts.textFile), "utf8");
1497
+ const form = new FormData();
1498
+ form.append("file", readFileAsBlob(opts.input));
1499
+ form.append("text", text);
1500
+ logger.debug(`Aligning ${opts.input} (${text.length} chars)`);
1501
+ const data = await apiPostMultipart("forced-alignment", form, apiKey, {
1502
+ timeoutMs: 6e5
1503
+ });
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));
1507
+ logger.success(`Saved alignment: ${dest}`);
1508
+ if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
1509
+ });
1510
+ }
1511
+
1512
+ // src/providers/elevenlabs/commands/clone.ts
1513
+ init_errors();
1514
+ function registerCloneCommand(parent) {
1515
+ parent.command("clone").description("Instant voice cloning from local audio samples").requiredOption("--name <n>", "Display name for the new voice").option("--files <paths...>", "One or more audio sample files (1-3 minutes total)").option("--audio <path>", "Single sample file (alias for --files)").option("--description <d>", "Voice description").option("--labels <kv>", `JSON object of labels, e.g. '{"accent":"american"}'`).option("--remove-background-noise", "Server-side noise removal", false).option("-v, --verbose", "Verbose logging").action(async (opts) => {
1516
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1517
+ const apiKey = requireElevenLabsKey();
1518
+ const files = opts.files ?? (opts.audio ? [opts.audio] : []);
1519
+ if (files.length === 0) {
1520
+ throw new ValidationError("Provide --files <p1> <p2>... or --audio <path>");
1521
+ }
1522
+ const form = new FormData();
1523
+ form.append("name", opts.name);
1524
+ if (opts.description) form.append("description", opts.description);
1525
+ if (opts.labels) {
1526
+ try {
1527
+ JSON.parse(opts.labels);
1528
+ form.append("labels", opts.labels);
1529
+ } catch {
1530
+ throw new ValidationError("--labels must be valid JSON");
1531
+ }
1532
+ }
1533
+ if (opts.removeBackgroundNoise) form.append("remove_background_noise", "true");
1534
+ for (const f of files) form.append("files", readFileAsBlob(f));
1535
+ logger.debug(`Cloning ${files.length} sample(s) as "${opts.name}"`);
1536
+ const data = await apiPostMultipart("voices/add", form, apiKey, {
1537
+ timeoutMs: 3e5
1538
+ });
1539
+ console.log(`Created voice: ${data.voice_id}`);
1540
+ if (data.requires_verification) {
1541
+ console.warn("Voice requires verification before use (PVC pipeline).");
1542
+ }
1543
+ });
1544
+ }
1545
+
1546
+ // src/providers/elevenlabs/commands/dub.ts
1547
+ init_errors();
1548
+ init_http_client();
1248
1549
  import fs9 from "fs";
1550
+ import path14 from "path";
1551
+ function registerDubCommand(parent) {
1552
+ 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
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1554
+ const apiKey = requireElevenLabsKey();
1555
+ if (!opts.input && !opts.sourceUrl) {
1556
+ throw new ValidationError("Provide --input <path> or --source-url <url>");
1557
+ }
1558
+ const form = new FormData();
1559
+ if (opts.input) form.append("file", readFileAsBlob(opts.input));
1560
+ if (opts.sourceUrl) form.append("source_url", opts.sourceUrl);
1561
+ form.append("target_lang", opts.targetLang);
1562
+ if (opts.sourceLang) form.append("source_lang", opts.sourceLang);
1563
+ if (opts.numSpeakers) form.append("num_speakers", opts.numSpeakers);
1564
+ if (opts.watermark) form.append("watermark", "true");
1565
+ if (opts.startTime) form.append("start_time", opts.startTime);
1566
+ if (opts.endTime) form.append("end_time", opts.endTime);
1567
+ if (opts.highestResolution) form.append("highest_resolution", "true");
1568
+ if (opts.name) form.append("name", opts.name);
1569
+ logger.debug(`Submitting dub -> ${opts.targetLang}`);
1570
+ const start = await apiPostMultipart("dubbing", form, apiKey, {
1571
+ timeoutMs: 6e5
1572
+ });
1573
+ console.log(
1574
+ `Dubbing job: ${start.dubbing_id} (expected ~${start.expected_duration_sec ?? "?"}s)`
1575
+ );
1576
+ if (opts.async && !opts.wait && !opts.download) {
1577
+ console.log("Run `multix elevenlabs dub-status <id>` to check progress.");
1578
+ return;
1579
+ }
1580
+ const final = await pollUntil(
1581
+ () => apiGet(`dubbing/${start.dubbing_id}`, apiKey),
1582
+ (s) => s.status === "dubbed",
1583
+ (s) => s.status === "failed",
1584
+ {
1585
+ intervalMs: Number.parseInt(opts.pollInterval, 10) * 1e3,
1586
+ timeoutMs: Number.parseInt(opts.waitTimeout, 10),
1587
+ logger,
1588
+ label: `dub ${start.dubbing_id}`
1589
+ }
1590
+ );
1591
+ logger.success(`Dub complete: ${start.dubbing_id}`);
1592
+ if (opts.download) {
1593
+ const dest = opts.output ? path14.resolve(opts.output) : path14.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
1594
+ const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
1595
+ await downloadDub(url, apiKey, dest);
1596
+ logger.success(`Saved: ${dest}`);
1597
+ console.log(`
1598
+ Dubbed file: ${dest}`);
1599
+ } else {
1600
+ console.log(`Languages ready: ${(final.target_languages ?? []).join(", ")}`);
1601
+ }
1602
+ });
1603
+ parent.command("dub-status <dubbingId>").description("Check dubbing status / list available languages").option("--download <lang>", "Download dubbed file for a language code").option("--output <path>", "Output path").action(async (dubbingId, opts) => {
1604
+ const apiKey = requireElevenLabsKey();
1605
+ const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
1606
+ console.log(JSON.stringify(status, null, 2));
1607
+ if (opts.download) {
1608
+ const dest = opts.output ? path14.resolve(opts.output) : path14.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
1609
+ const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
1610
+ await downloadDub(url, apiKey, dest);
1611
+ console.log(`Saved: ${dest}`);
1612
+ }
1613
+ });
1614
+ }
1615
+ async function downloadDub(url, apiKey, dest) {
1616
+ fs9.mkdirSync(path14.dirname(dest), { recursive: true });
1617
+ const res = await globalThis.fetch(url, {
1618
+ headers: { "xi-api-key": apiKey },
1619
+ signal: AbortSignal.timeout(6e5)
1620
+ });
1621
+ if (!res.ok || !res.body) {
1622
+ throw new Error(`Failed to download dub: HTTP ${res.status}`);
1623
+ }
1624
+ const arr = new Uint8Array(await res.arrayBuffer());
1625
+ fs9.writeFileSync(dest, arr);
1626
+ void downloadFile;
1627
+ }
1628
+
1629
+ // src/providers/elevenlabs/commands/isolate.ts
1630
+ function registerIsolateCommand(parent) {
1631
+ parent.command("isolate").description("Voice isolator: strip background noise from audio").requiredOption("--input <path>", "Source audio/video file").option("--output <path>", "Copy isolated audio to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1632
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1633
+ const apiKey = requireElevenLabsKey();
1634
+ const form = new FormData();
1635
+ form.append("audio", readFileAsBlob(opts.input));
1636
+ logger.debug(`Isolating ${opts.input}`);
1637
+ const bytes = await apiPostBinary("audio-isolation", form, apiKey, {
1638
+ timeoutMs: 6e5
1639
+ });
1640
+ const dest = saveBytes({
1641
+ bytes,
1642
+ task: "isolate",
1643
+ ext: "mp3",
1644
+ outputCopy: opts.output,
1645
+ logger
1646
+ });
1647
+ console.log(`
1648
+ Isolated audio: ${dest}`);
1649
+ });
1650
+ }
1651
+
1652
+ // src/providers/elevenlabs/commands/music.ts
1653
+ init_errors();
1654
+ import fs10 from "fs";
1655
+ import path15 from "path";
1656
+ function registerMusicCommand(parent) {
1657
+ 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
+ "--format <fmt>",
1659
+ `Output format (${[...ELEVENLABS_OUTPUT_FORMATS].join("|")})`,
1660
+ TASK_DEFAULTS.musicFormat
1661
+ ).option("--output <path>", "Copy to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1662
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1663
+ const apiKey = requireElevenLabsKey();
1664
+ if (!opts.prompt && !opts.plan) {
1665
+ throw new ValidationError("Provide --prompt or --plan");
1666
+ }
1667
+ const payload = { model_id: opts.model };
1668
+ if (opts.prompt) payload.prompt = opts.prompt;
1669
+ 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"));
1673
+ payload.composition_plan = planJson;
1674
+ }
1675
+ if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
1676
+ logger.debug(`Music gen with model ${opts.model}`);
1677
+ const bytes = await apiPostBinary("music", payload, apiKey, {
1678
+ query: { output_format: opts.format },
1679
+ timeoutMs: 6e5
1680
+ });
1681
+ const dest = saveBytes({
1682
+ bytes,
1683
+ task: "music",
1684
+ ext: extFromFormat(opts.format),
1685
+ outputCopy: opts.output,
1686
+ logger
1687
+ });
1688
+ console.log(`
1689
+ Generated music: ${dest}`);
1690
+ });
1691
+ }
1692
+
1693
+ // src/providers/elevenlabs/commands/sfx.ts
1694
+ function registerSfxCommand(parent) {
1695
+ parent.command("sfx").description("Generate sound effects from a text prompt").option("--text <str>", "Sound description (alias: --prompt)").option("--prompt <str>", "Alias for --text").option("--duration-seconds <n>", "Target duration 0.5..30 (auto if omitted)").option("--prompt-influence <n>", "0..1 (default 0.3)", "0.3").option("--loop", "Loop-friendly output", false).option(
1696
+ "--format <fmt>",
1697
+ `Output format (${[...ELEVENLABS_OUTPUT_FORMATS].join("|")})`,
1698
+ TASK_DEFAULTS.sfxFormat
1699
+ ).option("--output <path>", "Copy to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1700
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1701
+ const apiKey = requireElevenLabsKey();
1702
+ const text = opts.text ?? opts.prompt;
1703
+ if (!text) {
1704
+ const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
1705
+ throw new ValidationError2("--text or --prompt is required");
1706
+ }
1707
+ const payload = {
1708
+ text,
1709
+ prompt_influence: Number.parseFloat(opts.promptInfluence),
1710
+ loop: opts.loop
1711
+ };
1712
+ if (opts.durationSeconds) payload.duration_seconds = Number.parseFloat(opts.durationSeconds);
1713
+ logger.debug(`SFX: "${text.slice(0, 60)}..."`);
1714
+ const bytes = await apiPostBinary("sound-generation", payload, apiKey, {
1715
+ query: { output_format: opts.format },
1716
+ timeoutMs: 3e5
1717
+ });
1718
+ const dest = saveBytes({
1719
+ bytes,
1720
+ task: "sfx",
1721
+ ext: extFromFormat(opts.format),
1722
+ outputCopy: opts.output,
1723
+ logger
1724
+ });
1725
+ console.log(`
1726
+ Generated SFX: ${dest}`);
1727
+ });
1728
+ }
1729
+
1730
+ // src/providers/elevenlabs/commands/transcribe.ts
1731
+ import fs11 from "fs";
1732
+ import path16 from "path";
1733
+ function registerTranscribeCommand(parent) {
1734
+ parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
1735
+ "--model <id>",
1736
+ `STT model (${[...ELEVENLABS_STT_MODELS].join("|")})`,
1737
+ TASK_DEFAULTS.sttModel
1738
+ ).option("--language <code>", "ISO language code (e.g. en); auto-detect if omitted").option("--diarize", "Speaker diarization", false).option("--num-speakers <n>", "Hint for speaker count").option("--tag-audio-events", "Tag laughter/applause/etc.", false).option("--timestamps-granularity <g>", "none|word|character", "word").option("--format <f>", "text|json|srt|vtt", "text").option("--output <path>", "Save to this path (defaults to multix-output)").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1739
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1740
+ const apiKey = requireElevenLabsKey();
1741
+ const form = new FormData();
1742
+ form.append("file", readFileAsBlob(opts.input));
1743
+ form.append("model_id", opts.model);
1744
+ if (opts.language) form.append("language_code", opts.language);
1745
+ if (opts.diarize) form.append("diarize", "true");
1746
+ if (opts.numSpeakers) form.append("num_speakers", opts.numSpeakers);
1747
+ if (opts.tagAudioEvents) form.append("tag_audio_events", "true");
1748
+ form.append("timestamps_granularity", opts.timestampsGranularity);
1749
+ logger.debug(`Transcribing ${opts.input} (model=${opts.model})`);
1750
+ const data = await apiPostMultipart("speech-to-text", form, apiKey, {
1751
+ timeoutMs: 6e5
1752
+ });
1753
+ const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
1754
+ const dir = getOutputDir();
1755
+ const dest = opts.output ? path16.resolve(opts.output) : path16.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
1756
+ let body;
1757
+ switch (opts.format) {
1758
+ case "json":
1759
+ body = JSON.stringify(data, null, 2);
1760
+ break;
1761
+ case "srt":
1762
+ body = wordsToSrt(data.words ?? []);
1763
+ break;
1764
+ case "vtt":
1765
+ body = wordsToVtt(data.words ?? []);
1766
+ break;
1767
+ default:
1768
+ body = data.text ?? "";
1769
+ }
1770
+ fs11.mkdirSync(path16.dirname(dest), { recursive: true });
1771
+ fs11.writeFileSync(dest, body);
1772
+ logger.success(`Saved transcript: ${dest}`);
1773
+ if (data.language_code) {
1774
+ logger.info(
1775
+ `Detected language: ${data.language_code} (p=${data.language_probability ?? "?"})`
1776
+ );
1777
+ }
1778
+ if (opts.format === "text") console.log(`
1779
+ ${body.trim()}`);
1780
+ });
1781
+ }
1782
+ function fmtTimeSrt(s) {
1783
+ const hh = Math.floor(s / 3600).toString().padStart(2, "0");
1784
+ const mm = Math.floor(s % 3600 / 60).toString().padStart(2, "0");
1785
+ const ss = Math.floor(s % 60).toString().padStart(2, "0");
1786
+ const ms = Math.round((s - Math.floor(s)) * 1e3).toString().padStart(3, "0");
1787
+ return `${hh}:${mm}:${ss},${ms}`;
1788
+ }
1789
+ function fmtTimeVtt(s) {
1790
+ return fmtTimeSrt(s).replace(",", ".");
1791
+ }
1792
+ function wordsToSrt(words) {
1793
+ const lines = [];
1794
+ words.forEach((w, i) => {
1795
+ if (w.start === void 0 || w.end === void 0) return;
1796
+ lines.push(`${i + 1}
1797
+ ${fmtTimeSrt(w.start)} --> ${fmtTimeSrt(w.end)}
1798
+ ${w.text}
1799
+ `);
1800
+ });
1801
+ return lines.join("\n");
1802
+ }
1803
+ function wordsToVtt(words) {
1804
+ const lines = ["WEBVTT", ""];
1805
+ for (const w of words) {
1806
+ if (w.start === void 0 || w.end === void 0) continue;
1807
+ lines.push(`${fmtTimeVtt(w.start)} --> ${fmtTimeVtt(w.end)}`);
1808
+ lines.push(w.text);
1809
+ lines.push("");
1810
+ }
1811
+ return lines.join("\n");
1812
+ }
1813
+
1814
+ // src/providers/elevenlabs/commands/tts.ts
1815
+ init_errors();
1816
+ function registerTtsCommand(parent) {
1817
+ parent.command("tts").description("Text-to-speech (returns audio synchronously)").option("--text <str>", "Text to speak (use --text or --prompt)").option("--prompt <str>", "Alias for --text").option("--voice <id>", "Voice ID", TASK_DEFAULTS.ttsVoice).option(
1818
+ "--model <id>",
1819
+ `TTS model (${[...ELEVENLABS_TTS_MODELS].join("|")})`,
1820
+ TASK_DEFAULTS.ttsModel
1821
+ ).option(
1822
+ "--format <fmt>",
1823
+ `Output format (${[...ELEVENLABS_OUTPUT_FORMATS].join("|")})`,
1824
+ TASK_DEFAULTS.ttsFormat
1825
+ ).option("--stability <n>", "Voice stability 0..1", "0.5").option("--similarity-boost <n>", "Voice similarity boost 0..1", "0.75").option("--style <n>", "Style exaggeration 0..1", "0.0").option("--no-speaker-boost", "Disable speaker boost (default: enabled)").option("--language-code <code>", "ISO language code (Flash/Turbo/v3 only)").option("--seed <n>", "Determinism seed (0..4294967295)").option("--previous-text <str>", "Preceding text for context continuity").option("--next-text <str>", "Following text for context continuity").option("--output <path>", "Copy generated audio to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1826
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1827
+ const apiKey = requireElevenLabsKey();
1828
+ const text = opts.text ?? opts.prompt;
1829
+ if (!text) throw new ValidationError("Either --text or --prompt is required");
1830
+ if (!opts.voice) throw new ValidationError("--voice <id> is required");
1831
+ const payload = {
1832
+ text,
1833
+ model_id: opts.model,
1834
+ voice_settings: {
1835
+ stability: Number.parseFloat(opts.stability),
1836
+ similarity_boost: Number.parseFloat(opts.similarityBoost),
1837
+ style: Number.parseFloat(opts.style),
1838
+ use_speaker_boost: opts.speakerBoost
1839
+ }
1840
+ };
1841
+ if (opts.languageCode) payload.language_code = opts.languageCode;
1842
+ if (opts.seed !== void 0) payload.seed = Number.parseInt(opts.seed, 10);
1843
+ if (opts.previousText) payload.previous_text = opts.previousText;
1844
+ if (opts.nextText) payload.next_text = opts.nextText;
1845
+ logger.debug(`TTS voice=${opts.voice} model=${opts.model} fmt=${opts.format}`);
1846
+ const bytes = await apiPostBinary(`text-to-speech/${opts.voice}`, payload, apiKey, {
1847
+ query: { output_format: opts.format }
1848
+ });
1849
+ const dest = saveBytes({
1850
+ bytes,
1851
+ task: "tts",
1852
+ ext: extFromFormat(opts.format),
1853
+ outputCopy: opts.output,
1854
+ logger
1855
+ });
1856
+ console.log(`
1857
+ Generated audio: ${dest}`);
1858
+ });
1859
+ }
1860
+
1861
+ // src/providers/elevenlabs/commands/voice-changer.ts
1862
+ init_errors();
1863
+ function registerVoiceChangerCommand(parent) {
1864
+ parent.command("voice-changer").description("Speech-to-speech: transform an audio file to use a different voice").requiredOption("--input <path>", "Source audio file").requiredOption("--voice <id>", "Target voice ID").option("--model <id>", "Voice changer model", TASK_DEFAULTS.voiceChangerModel).option(
1865
+ "--format <fmt>",
1866
+ `Output format (${[...ELEVENLABS_OUTPUT_FORMATS].join("|")})`,
1867
+ TASK_DEFAULTS.ttsFormat
1868
+ ).option("--remove-background-noise", "Server-side noise removal", false).option("--stability <n>", "0..1", "0.5").option("--similarity-boost <n>", "0..1", "0.75").option("--style <n>", "0..1", "0.0").option("--output <path>", "Copy output to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1869
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1870
+ const apiKey = requireElevenLabsKey();
1871
+ const form = new FormData();
1872
+ form.append("audio", readFileAsBlob(opts.input));
1873
+ form.append("model_id", opts.model);
1874
+ form.append(
1875
+ "voice_settings",
1876
+ JSON.stringify({
1877
+ stability: Number.parseFloat(opts.stability),
1878
+ similarity_boost: Number.parseFloat(opts.similarityBoost),
1879
+ style: Number.parseFloat(opts.style)
1880
+ })
1881
+ );
1882
+ if (opts.removeBackgroundNoise) form.append("remove_background_noise", "true");
1883
+ logger.debug(`Voice changer voice=${opts.voice} input=${opts.input}`);
1884
+ const bytes = await apiPostBinary(`speech-to-speech/${opts.voice}`, form, apiKey, {
1885
+ query: { output_format: opts.format },
1886
+ timeoutMs: 3e5
1887
+ });
1888
+ if (!bytes || bytes.length === 0) {
1889
+ throw new ValidationError("Empty response from voice-changer");
1890
+ }
1891
+ const dest = saveBytes({
1892
+ bytes,
1893
+ task: "voice_changer",
1894
+ ext: extFromFormat(opts.format),
1895
+ outputCopy: opts.output,
1896
+ logger
1897
+ });
1898
+ console.log(`
1899
+ Generated audio: ${dest}`);
1900
+ });
1901
+ }
1902
+
1903
+ // src/providers/elevenlabs/commands/voices.ts
1904
+ init_errors();
1905
+ function registerVoicesCommand(parent) {
1906
+ const voices = parent.command("voices").description("Voice library: list, get, search, design");
1907
+ voices.command("list").description("List voices in your account").option("--category <c>", "Filter category (cloned, generated, premade, professional)").option("--page-size <n>", "Voices per page", "100").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1908
+ const apiKey = requireElevenLabsKey();
1909
+ const data = await apiGet("voices", apiKey, {
1910
+ category: opts.category,
1911
+ page_size: opts.pageSize
1912
+ });
1913
+ for (const v of data.voices) {
1914
+ const labels = v.labels ? ` [${Object.values(v.labels).join(", ")}]` : "";
1915
+ console.log(`${v.voice_id} ${v.name} (${v.category ?? "?"})${labels}`);
1916
+ }
1917
+ console.log(`
1918
+ ${data.voices.length} voices`);
1919
+ });
1920
+ voices.command("get <voiceId>").description("Get a voice's metadata").action(async (voiceId) => {
1921
+ const apiKey = requireElevenLabsKey();
1922
+ const data = await apiGet(`voices/${voiceId}`, apiKey);
1923
+ console.log(JSON.stringify(data, null, 2));
1924
+ });
1925
+ voices.command("search").description("Search the public Voices Library (shared voices)").option("--search <term>", "Search query").option("--language <code>", "Language filter, e.g. en, es").option("--gender <g>", "male|female|neutral").option("--age <a>", "young|middle_aged|old").option("--accent <a>", "Accent filter, e.g. american, british").option("--category <c>", "Category filter").option("--page-size <n>", "Results per page", "30").action(async (opts) => {
1926
+ const apiKey = requireElevenLabsKey();
1927
+ const data = await apiGet("shared-voices", apiKey, {
1928
+ search: opts.search,
1929
+ language: opts.language,
1930
+ gender: opts.gender,
1931
+ age: opts.age,
1932
+ accent: opts.accent,
1933
+ category: opts.category,
1934
+ page_size: opts.pageSize
1935
+ });
1936
+ for (const v of data.voices) {
1937
+ console.log(`${v.voice_id} ${v.name} ${v.description ?? ""}`);
1938
+ }
1939
+ console.log(`
1940
+ ${data.voices.length} voices${data.has_more ? " (more available)" : ""}`);
1941
+ });
1942
+ voices.command("design").description("Design a voice from text description (returns previews)").requiredOption("--description <str>", "Voice description (e.g. 'friendly female narrator')").option("--text <str>", "Sample text to generate previews from").option("--auto-text", "Let ElevenLabs generate sample text", false).option("--model <id>", "Voice design model", "eleven_multilingual_ttv_v2").option("--output-prefix <p>", "Filename prefix (default: design)", "design").option("-v, --verbose", "Verbose logging").action(async (opts) => {
1943
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1944
+ const apiKey = requireElevenLabsKey();
1945
+ if (!opts.autoText && !opts.text) {
1946
+ throw new ValidationError("Provide --text or pass --auto-text");
1947
+ }
1948
+ const payload = {
1949
+ voice_description: opts.description,
1950
+ model_id: opts.model,
1951
+ auto_generate_text: opts.autoText
1952
+ };
1953
+ if (opts.text) payload.text = opts.text;
1954
+ const data = await apiPostJson(
1955
+ "text-to-voice/create-previews",
1956
+ payload,
1957
+ apiKey
1958
+ );
1959
+ logger.success(`${data.previews.length} previews generated`);
1960
+ data.previews.forEach((p, i) => {
1961
+ const bytes = Buffer.from(p.audio_base_64, "base64");
1962
+ const dest = saveBytes({
1963
+ bytes,
1964
+ task: `${opts.outputPrefix}_${i + 1}_${p.generated_voice_id}`,
1965
+ ext: extFromFormat(TASK_DEFAULTS.ttsFormat),
1966
+ logger
1967
+ });
1968
+ console.log(`Preview ${i + 1}: voice_id=${p.generated_voice_id} file=${dest}`);
1969
+ });
1970
+ console.log(
1971
+ '\nTo finalize: multix elevenlabs voices create-from-preview --generated-voice-id <id> --name "..." --description "..."'
1972
+ );
1973
+ });
1974
+ voices.command("create-from-preview").description("Persist a designed voice into your library").requiredOption("--generated-voice-id <id>", "ID returned by `voices design`").requiredOption("--name <n>", "Display name").requiredOption("--description <d>", "Voice description").option("--played-not-selected-voice-ids <csv>", "Other previews you considered (CSV)").action(async (opts) => {
1975
+ const apiKey = requireElevenLabsKey();
1976
+ const payload = {
1977
+ generated_voice_id: opts.generatedVoiceId,
1978
+ voice_name: opts.name,
1979
+ voice_description: opts.description
1980
+ };
1981
+ if (opts.playedNotSelectedVoiceIds) {
1982
+ payload.played_not_selected_voice_ids = opts.playedNotSelectedVoiceIds.split(",");
1983
+ }
1984
+ const data = await apiPostJson(
1985
+ "text-to-voice/create-voice-from-preview",
1986
+ payload,
1987
+ apiKey
1988
+ );
1989
+ console.log(`Created voice: ${data.voice_id}`);
1990
+ });
1991
+ voices.command("delete <voiceId>").description("Delete a voice from your library").action(async (voiceId) => {
1992
+ const apiKey = requireElevenLabsKey();
1993
+ await apiDelete(`voices/${voiceId}`, apiKey);
1994
+ console.log(`Deleted: ${voiceId}`);
1995
+ });
1996
+ }
1997
+
1998
+ // src/providers/elevenlabs/commands/index.ts
1999
+ function registerElevenLabsCommands(program2) {
2000
+ const el = program2.command("elevenlabs").description(
2001
+ "ElevenLabs: TTS, voices, cloning, STT, voice changer, SFX, music, dubbing, isolation, alignment"
2002
+ );
2003
+ registerTtsCommand(el);
2004
+ registerVoicesCommand(el);
2005
+ registerCloneCommand(el);
2006
+ registerVoiceChangerCommand(el);
2007
+ registerTranscribeCommand(el);
2008
+ registerSfxCommand(el);
2009
+ registerMusicCommand(el);
2010
+ registerDubCommand(el);
2011
+ registerIsolateCommand(el);
2012
+ registerAlignCommand(el);
2013
+ registerAccountCommands(el);
2014
+ }
2015
+
2016
+ // src/providers/gemini/commands/analyze.ts
2017
+ init_errors();
2018
+ import fs14 from "fs";
1249
2019
 
1250
2020
  // src/providers/gemini/client.ts
1251
- import fs7 from "fs";
1252
- import path13 from "path";
2021
+ import fs12 from "fs";
2022
+ import path18 from "path";
1253
2023
  init_errors();
1254
2024
  init_http_client();
1255
2025
 
1256
2026
  // src/providers/gemini/task-resolver.ts
1257
- import path12 from "path";
2027
+ import path17 from "path";
1258
2028
  var MIME_MAP = {
1259
2029
  // Audio
1260
2030
  ".mp3": "audio/mp3",
@@ -1295,7 +2065,7 @@ var MIME_MAP = {
1295
2065
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1296
2066
  };
1297
2067
  function getMimeType(filePath) {
1298
- const ext = path12.extname(filePath).toLowerCase();
2068
+ const ext = path17.extname(filePath).toLowerCase();
1299
2069
  return MIME_MAP[ext] ?? "application/octet-stream";
1300
2070
  }
1301
2071
  function requiresProcessingWait(mimeType) {
@@ -1314,15 +2084,15 @@ function requireApiKey() {
1314
2084
  );
1315
2085
  return key;
1316
2086
  }
1317
- function authHeaders(apiKey) {
2087
+ function authHeaders2(apiKey) {
1318
2088
  return { "x-goog-api-key": apiKey };
1319
2089
  }
1320
2090
  async function uploadFile(filePath, opts = {}) {
1321
2091
  const apiKey = requireApiKey();
1322
2092
  const { timeoutMs = 3e5, logger } = opts;
1323
2093
  const mimeType = getMimeType(filePath);
1324
- const fileBytes = fs7.readFileSync(filePath);
1325
- const displayName = path13.basename(filePath);
2094
+ const fileBytes = fs12.readFileSync(filePath);
2095
+ const displayName = path18.basename(filePath);
1326
2096
  const boundary = `multix_${Date.now()}`;
1327
2097
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
1328
2098
  const body = Buffer.concat([
@@ -1367,7 +2137,7 @@ Content-Type: ${mimeType}\r
1367
2137
  await sleep(2e3);
1368
2138
  fileRef = await httpJson({
1369
2139
  url: `${BASE}/v1beta/${fileRef.name}`,
1370
- headers: authHeaders(apiKey)
2140
+ headers: authHeaders2(apiKey)
1371
2141
  }).then((r) => r.file);
1372
2142
  logger?.debug(`Processing... state=${fileRef.state}`);
1373
2143
  }
@@ -1384,7 +2154,7 @@ async function generateContent(req) {
1384
2154
  return httpJson({
1385
2155
  url: `${GENERATE_BASE}/${req.model}:generateContent`,
1386
2156
  method: "POST",
1387
- headers: authHeaders(apiKey),
2157
+ headers: authHeaders2(apiKey),
1388
2158
  body: {
1389
2159
  contents: req.contents,
1390
2160
  ...req.generationConfig ? { generationConfig: req.generationConfig } : {}
@@ -1411,12 +2181,12 @@ function extractImages(resp) {
1411
2181
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
1412
2182
  }
1413
2183
  function inlinePart(filePath) {
1414
- const data = fs7.readFileSync(filePath).toString("base64");
2184
+ const data = fs12.readFileSync(filePath).toString("base64");
1415
2185
  const mimeType = getMimeType(filePath);
1416
2186
  return { inlineData: { mimeType, data } };
1417
2187
  }
1418
2188
  function shouldUseFileApi(filePath) {
1419
- const stat = fs7.statSync(filePath);
2189
+ const stat = fs12.statSync(filePath);
1420
2190
  return stat.size > 20 * 1024 * 1024;
1421
2191
  }
1422
2192
  function sleep(ms) {
@@ -1469,13 +2239,13 @@ function getDefaultModel(task) {
1469
2239
  }
1470
2240
 
1471
2241
  // src/providers/gemini/result-formatter.ts
1472
- import fs8 from "fs";
1473
- import path14 from "path";
2242
+ import fs13 from "fs";
2243
+ import path19 from "path";
1474
2244
  function formatResults(results, format, outputPath) {
1475
2245
  const content = renderFormat(results, format);
1476
2246
  if (outputPath) {
1477
- fs8.mkdirSync(path14.dirname(path14.resolve(outputPath)), { recursive: true });
1478
- fs8.writeFileSync(outputPath, content, "utf8");
2247
+ fs13.mkdirSync(path19.dirname(path19.resolve(outputPath)), { recursive: true });
2248
+ fs13.writeFileSync(outputPath, content, "utf8");
1479
2249
  } else {
1480
2250
  process.stdout.write(content);
1481
2251
  }
@@ -1542,7 +2312,7 @@ function registerAnalyzeCommand(parent) {
1542
2312
  const logger = createLogger({ verbose: opts.verbose ?? false });
1543
2313
  const model = opts.model ?? getDefaultModel("analyze");
1544
2314
  for (const f of opts.files) {
1545
- if (!fs9.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2315
+ if (!fs14.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
1546
2316
  }
1547
2317
  const results = [];
1548
2318
  for (let i = 0; i < opts.files.length; i++) {
@@ -1588,14 +2358,14 @@ function registerAnalyzeCommand(parent) {
1588
2358
 
1589
2359
  // src/providers/gemini/commands/extract.ts
1590
2360
  init_errors();
1591
- import fs10 from "fs";
2361
+ import fs15 from "fs";
1592
2362
  function registerExtractCommand(parent) {
1593
2363
  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(
1594
2364
  async (opts) => {
1595
2365
  const logger = createLogger({ verbose: opts.verbose ?? false });
1596
2366
  const model = opts.model ?? getDefaultModel("extract");
1597
2367
  for (const f of opts.files) {
1598
- if (!fs10.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2368
+ if (!fs15.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
1599
2369
  }
1600
2370
  const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
1601
2371
  const results = [];
@@ -1637,8 +2407,8 @@ function registerExtractCommand(parent) {
1637
2407
  init_errors();
1638
2408
 
1639
2409
  // src/providers/gemini/generators/speech.ts
1640
- import fs11 from "fs";
1641
- import path15 from "path";
2410
+ import fs16 from "fs";
2411
+ import path20 from "path";
1642
2412
 
1643
2413
  // src/providers/gemini/voices.ts
1644
2414
  var GEMINI_TTS_VOICES = [
@@ -1721,12 +2491,12 @@ async function generateGeminiSpeech(opts) {
1721
2491
  const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
1722
2492
  const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
1723
2493
  const outDir = getOutputDir();
1724
- const dest = path15.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
1725
- fs11.writeFileSync(dest, outBytes);
2494
+ const dest = path20.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
2495
+ fs16.writeFileSync(dest, outBytes);
1726
2496
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
1727
2497
  if (output) {
1728
- fs11.mkdirSync(path15.dirname(path15.resolve(output)), { recursive: true });
1729
- fs11.copyFileSync(dest, output);
2498
+ fs16.mkdirSync(path20.dirname(path20.resolve(output)), { recursive: true });
2499
+ fs16.copyFileSync(dest, output);
1730
2500
  logger?.success(`Copied to: ${output}`);
1731
2501
  }
1732
2502
  return { status: "success", generatedAudio: dest, model, mimeType: audio.mimeType };
@@ -1825,8 +2595,8 @@ function collectSpeakers(value, prev) {
1825
2595
  }
1826
2596
 
1827
2597
  // src/providers/gemini/commands/generate-video.ts
1828
- import fs12 from "fs";
1829
- import path16 from "path";
2598
+ import fs17 from "fs";
2599
+ import path21 from "path";
1830
2600
  init_errors();
1831
2601
  init_http_client();
1832
2602
  var BASE2 = "https://generativelanguage.googleapis.com";
@@ -1860,10 +2630,10 @@ function registerGenerateVideoCommand(parent) {
1860
2630
  const contentParts = [{ text: opts.prompt }];
1861
2631
  if (opts.referenceImages?.length) {
1862
2632
  for (const imgPath of opts.referenceImages.slice(0, 2)) {
1863
- if (!fs12.existsSync(imgPath))
2633
+ if (!fs17.existsSync(imgPath))
1864
2634
  throw new ValidationError(`Reference image not found: ${imgPath}`);
1865
- const data = fs12.readFileSync(imgPath).toString("base64");
1866
- const ext = path16.extname(imgPath).toLowerCase();
2635
+ const data = fs17.readFileSync(imgPath).toString("base64");
2636
+ const ext = path21.extname(imgPath).toLowerCase();
1867
2637
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
1868
2638
  contentParts.push({ inlineData: { mimeType, data } });
1869
2639
  }
@@ -1912,13 +2682,13 @@ function registerGenerateVideoCommand(parent) {
1912
2682
  );
1913
2683
  }
1914
2684
  const outDir = getOutputDir();
1915
- const dest = path16.join(outDir, `veo_generated_${Date.now()}.mp4`);
2685
+ const dest = path21.join(outDir, `veo_generated_${Date.now()}.mp4`);
1916
2686
  const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
1917
2687
  await downloadFile2(videoUri, dest);
1918
2688
  logger.success(`Saved video: ${dest}`);
1919
2689
  if (opts.output) {
1920
- fs12.mkdirSync(path16.dirname(path16.resolve(opts.output)), { recursive: true });
1921
- fs12.copyFileSync(dest, opts.output);
2690
+ fs17.mkdirSync(path21.dirname(path21.resolve(opts.output)), { recursive: true });
2691
+ fs17.copyFileSync(dest, opts.output);
1922
2692
  logger.success(`Copied to: ${opts.output}`);
1923
2693
  }
1924
2694
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -1937,8 +2707,8 @@ function sleep2(ms) {
1937
2707
 
1938
2708
  // src/providers/gemini/commands/generate.ts
1939
2709
  init_errors();
1940
- import fs13 from "fs";
1941
- import path17 from "path";
2710
+ import fs18 from "fs";
2711
+ import path22 from "path";
1942
2712
  function registerGenerateCommand(parent) {
1943
2713
  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(
1944
2714
  async (opts) => {
@@ -1980,8 +2750,8 @@ function registerGenerateCommand(parent) {
1980
2750
  const outDir = getOutputDir();
1981
2751
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
1982
2752
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
1983
- const dest = path17.join(outDir, fname);
1984
- fs13.writeFileSync(dest, Buffer.from(img.data, "base64"));
2753
+ const dest = path22.join(outDir, fname);
2754
+ fs18.writeFileSync(dest, Buffer.from(img.data, "base64"));
1985
2755
  savedFiles.push(dest);
1986
2756
  logger.success(`Saved: ${dest}`);
1987
2757
  }
@@ -1996,8 +2766,8 @@ function registerGenerateCommand(parent) {
1996
2766
  process.exit(1);
1997
2767
  }
1998
2768
  if (opts.output && savedFiles[0]) {
1999
- fs13.mkdirSync(path17.dirname(path17.resolve(opts.output)), { recursive: true });
2000
- fs13.copyFileSync(savedFiles[0], opts.output);
2769
+ fs18.mkdirSync(path22.dirname(path22.resolve(opts.output)), { recursive: true });
2770
+ fs18.copyFileSync(savedFiles[0], opts.output);
2001
2771
  logger.success(`Copied to: ${opts.output}`);
2002
2772
  }
2003
2773
  console.log(`
@@ -2008,8 +2778,8 @@ Generated ${savedFiles.length} image(s):`);
2008
2778
  }
2009
2779
 
2010
2780
  // src/providers/gemini/commands/image-to-image.ts
2011
- import fs14 from "fs";
2012
- import path18 from "path";
2781
+ import fs19 from "fs";
2782
+ import path23 from "path";
2013
2783
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
2014
2784
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
2015
2785
  function registerGeminiImageToImageCommand(parent) {
@@ -2070,14 +2840,14 @@ function registerGeminiImageToImageCommand(parent) {
2070
2840
  const img = images[i];
2071
2841
  if (!img) continue;
2072
2842
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
2073
- const dest = path18.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
2074
- fs14.writeFileSync(dest, Buffer.from(img.data, "base64"));
2843
+ const dest = path23.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
2844
+ fs19.writeFileSync(dest, Buffer.from(img.data, "base64"));
2075
2845
  saved.push(dest);
2076
2846
  logger.success(`Saved: ${dest}`);
2077
2847
  }
2078
2848
  if (opts.output && saved[0]) {
2079
- fs14.mkdirSync(path18.dirname(path18.resolve(opts.output)), { recursive: true });
2080
- fs14.copyFileSync(saved[0], opts.output);
2849
+ fs19.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
2850
+ fs19.copyFileSync(saved[0], opts.output);
2081
2851
  logger.success(`Copied to: ${opts.output}`);
2082
2852
  }
2083
2853
  console.log(`
@@ -2104,7 +2874,7 @@ function sniffImageMime(buf, url) {
2104
2874
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
2105
2875
  return "image/webp";
2106
2876
  }
2107
- const ext = path18.extname(new URL(url).pathname).toLowerCase();
2877
+ const ext = path23.extname(new URL(url).pathname).toLowerCase();
2108
2878
  if (ext === ".png") return "image/png";
2109
2879
  if (ext === ".webp") return "image/webp";
2110
2880
  if (ext === ".gif") return "image/gif";
@@ -2112,17 +2882,17 @@ function sniffImageMime(buf, url) {
2112
2882
  }
2113
2883
 
2114
2884
  // src/providers/gemini/commands/image-to-video.ts
2115
- import fs15 from "fs";
2116
- import path19 from "path";
2885
+ import fs20 from "fs";
2886
+ import path24 from "path";
2117
2887
  init_errors();
2118
2888
  init_http_client();
2119
2889
  var BASE3 = "https://generativelanguage.googleapis.com";
2120
2890
  function imagePart(imgPath) {
2121
- if (!fs15.existsSync(imgPath)) {
2891
+ if (!fs20.existsSync(imgPath)) {
2122
2892
  throw new ValidationError(`Image not found: ${imgPath}`);
2123
2893
  }
2124
- const data = fs15.readFileSync(imgPath).toString("base64");
2125
- const ext = path19.extname(imgPath).toLowerCase();
2894
+ const data = fs20.readFileSync(imgPath).toString("base64");
2895
+ const ext = path24.extname(imgPath).toLowerCase();
2126
2896
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
2127
2897
  return { inlineData: { mimeType, data } };
2128
2898
  }
@@ -2195,12 +2965,12 @@ function registerGeminiImageToVideoCommand(parent) {
2195
2965
  "gemini"
2196
2966
  );
2197
2967
  }
2198
- const dest = path19.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
2968
+ const dest = path24.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
2199
2969
  await downloadFile(videoUri, dest);
2200
2970
  logger.success(`Saved video: ${dest}`);
2201
2971
  if (opts.output) {
2202
- fs15.mkdirSync(path19.dirname(path19.resolve(opts.output)), { recursive: true });
2203
- fs15.copyFileSync(dest, opts.output);
2972
+ fs20.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
2973
+ fs20.copyFileSync(dest, opts.output);
2204
2974
  logger.success(`Copied to: ${opts.output}`);
2205
2975
  }
2206
2976
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -2216,14 +2986,14 @@ Generated video: ${dest}`);
2216
2986
 
2217
2987
  // src/providers/gemini/commands/transcribe.ts
2218
2988
  init_errors();
2219
- import fs16 from "fs";
2220
- function registerTranscribeCommand(parent) {
2989
+ import fs21 from "fs";
2990
+ function registerTranscribeCommand2(parent) {
2221
2991
  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(
2222
2992
  async (opts) => {
2223
2993
  const logger = createLogger({ verbose: opts.verbose ?? false });
2224
2994
  const model = opts.model ?? getDefaultModel("transcribe");
2225
2995
  for (const f of opts.files) {
2226
- if (!fs16.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2996
+ if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2227
2997
  }
2228
2998
  const results = [];
2229
2999
  for (let i = 0; i < opts.files.length; i++) {
@@ -2270,7 +3040,7 @@ function registerGeminiCommands(program2) {
2270
3040
  "Gemini AI operations: analyze, transcribe, extract, generate images/video/speech"
2271
3041
  );
2272
3042
  registerAnalyzeCommand(gemini);
2273
- registerTranscribeCommand(gemini);
3043
+ registerTranscribeCommand2(gemini);
2274
3044
  registerExtractCommand(gemini);
2275
3045
  registerGenerateCommand(gemini);
2276
3046
  registerGeminiImageToImageCommand(gemini);
@@ -2281,8 +3051,8 @@ function registerGeminiCommands(program2) {
2281
3051
 
2282
3052
  // src/providers/leonardo/commands/generate.ts
2283
3053
  init_http_client();
2284
- import fs17 from "fs";
2285
- import path20 from "path";
3054
+ import fs22 from "fs";
3055
+ import path25 from "path";
2286
3056
 
2287
3057
  // src/providers/leonardo/client.ts
2288
3058
  init_errors();
@@ -2356,11 +3126,11 @@ var LeonardoClient = class {
2356
3126
  }
2357
3127
  apiKey;
2358
3128
  baseUrl;
2359
- async request(path36, opts = {}) {
3129
+ async request(path41, opts = {}) {
2360
3130
  const base = this.baseUrl.replace(/\/$/, "");
2361
- const v2Override = path36.startsWith("v2:");
3131
+ const v2Override = path41.startsWith("v2:");
2362
3132
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
2363
- const finalPath = v2Override ? path36.slice(3) : path36;
3133
+ const finalPath = v2Override ? path41.slice(3) : path41;
2364
3134
  const url = new URL(finalBase + finalPath);
2365
3135
  if (opts.query) {
2366
3136
  for (const [k, v] of Object.entries(opts.query)) {
@@ -2379,14 +3149,14 @@ var LeonardoClient = class {
2379
3149
  timeoutMs: opts.timeoutMs ?? 12e4
2380
3150
  });
2381
3151
  }
2382
- get(path36, query, logger) {
2383
- return this.request(path36, { method: "GET", query, logger });
3152
+ get(path41, query, logger) {
3153
+ return this.request(path41, { method: "GET", query, logger });
2384
3154
  }
2385
- post(path36, body, logger) {
2386
- return this.request(path36, { method: "POST", body, logger });
3155
+ post(path41, body, logger) {
3156
+ return this.request(path41, { method: "POST", body, logger });
2387
3157
  }
2388
- delete(path36, logger) {
2389
- return this.request(path36, { method: "DELETE", logger });
3158
+ delete(path41, logger) {
3159
+ return this.request(path41, { method: "DELETE", logger });
2390
3160
  }
2391
3161
  };
2392
3162
  function createLeonardoClient() {
@@ -2397,7 +3167,7 @@ function createLeonardoClient() {
2397
3167
  function extFromUrl(url, fallback = ".png") {
2398
3168
  try {
2399
3169
  const u = new URL(url);
2400
- const e = path20.extname(u.pathname);
3170
+ const e = path25.extname(u.pathname);
2401
3171
  return e.length > 0 ? e : fallback;
2402
3172
  } catch {
2403
3173
  return fallback;
@@ -2482,7 +3252,7 @@ function registerLeonardoGenerateCommand(parent) {
2482
3252
  if (!img) continue;
2483
3253
  const ext = extFromUrl(img.url);
2484
3254
  const filename = autoFilename(generationId, i + 1, ext);
2485
- const filepath = path20.join(outDir, filename);
3255
+ const filepath = path25.join(outDir, filename);
2486
3256
  try {
2487
3257
  await downloadFile(img.url, filepath);
2488
3258
  saved.push(filepath);
@@ -2492,9 +3262,9 @@ function registerLeonardoGenerateCommand(parent) {
2492
3262
  }
2493
3263
  }
2494
3264
  if (opts.output && saved[0]) {
2495
- const dest = path20.resolve(opts.output);
2496
- fs17.mkdirSync(path20.dirname(dest), { recursive: true });
2497
- fs17.copyFileSync(saved[0], dest);
3265
+ const dest = path25.resolve(opts.output);
3266
+ fs22.mkdirSync(path25.dirname(dest), { recursive: true });
3267
+ fs22.copyFileSync(saved[0], dest);
2498
3268
  logger.success(`Copied to ${dest}`);
2499
3269
  }
2500
3270
  console.log(`
@@ -2506,11 +3276,11 @@ Generated ${saved.length} image(s):`);
2506
3276
 
2507
3277
  // src/providers/leonardo/commands/image-to-image.ts
2508
3278
  init_http_client();
2509
- import fs18 from "fs";
2510
- import path21 from "path";
3279
+ import fs23 from "fs";
3280
+ import path26 from "path";
2511
3281
  function extFromUrl2(url, fallback = ".png") {
2512
3282
  try {
2513
- return path21.extname(new URL(url).pathname) || fallback;
3283
+ return path26.extname(new URL(url).pathname) || fallback;
2514
3284
  } catch {
2515
3285
  return fallback;
2516
3286
  }
@@ -2572,7 +3342,7 @@ function registerLeonardoImageToImageCommand(parent) {
2572
3342
  if (!img) continue;
2573
3343
  const ext = extFromUrl2(img.url);
2574
3344
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
2575
- const filepath = path21.join(outDir, filename);
3345
+ const filepath = path26.join(outDir, filename);
2576
3346
  try {
2577
3347
  await downloadFile(img.url, filepath);
2578
3348
  saved.push(filepath);
@@ -2582,9 +3352,9 @@ function registerLeonardoImageToImageCommand(parent) {
2582
3352
  }
2583
3353
  }
2584
3354
  if (opts.output && saved[0]) {
2585
- const dest = path21.resolve(opts.output);
2586
- fs18.mkdirSync(path21.dirname(dest), { recursive: true });
2587
- fs18.copyFileSync(saved[0], dest);
3355
+ const dest = path26.resolve(opts.output);
3356
+ fs23.mkdirSync(path26.dirname(dest), { recursive: true });
3357
+ fs23.copyFileSync(saved[0], dest);
2588
3358
  logger.success(`Copied to ${dest}`);
2589
3359
  }
2590
3360
  console.log(`
@@ -2596,7 +3366,7 @@ Generated ${saved.length} image(s):`);
2596
3366
 
2597
3367
  // src/providers/leonardo/commands/video.ts
2598
3368
  init_http_client();
2599
- import path22 from "path";
3369
+ import path27 from "path";
2600
3370
  function v2DimsFor(resolution) {
2601
3371
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
2602
3372
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -2707,7 +3477,7 @@ async function pollAndDownload(client, jobId, opts) {
2707
3477
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
2708
3478
  process.exit(1);
2709
3479
  }
2710
- const outPath = opts.output ?? path22.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
3480
+ const outPath = opts.output ?? path27.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
2711
3481
  await downloadFile(videoUrl, outPath);
2712
3482
  logger.success(`Saved ${outPath}`);
2713
3483
  await maybeDownloadThumb(gen, outPath, {
@@ -2814,7 +3584,7 @@ function registerLeonardoModelsCommand(parent) {
2814
3584
 
2815
3585
  // src/providers/leonardo/commands/status.ts
2816
3586
  init_http_client();
2817
- import path23 from "path";
3587
+ import path28 from "path";
2818
3588
  var VIDEO_KEYS = ["motionMP4URL", "url"];
2819
3589
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
2820
3590
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -2879,7 +3649,7 @@ function registerLeonardoStatusCommand(parent) {
2879
3649
  const outDir = getOutputDir();
2880
3650
  let idx = 0;
2881
3651
  for (const url of videos) {
2882
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path23.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
3652
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
2883
3653
  await downloadFile(url, dest);
2884
3654
  logger.success(`Saved ${dest}`);
2885
3655
  await maybeDownloadThumb(gen, dest, {
@@ -2892,7 +3662,7 @@ function registerLeonardoStatusCommand(parent) {
2892
3662
  for (const url of images) {
2893
3663
  const m = url.match(IMAGE_EXT_RE2);
2894
3664
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
2895
- const dest = path23.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
3665
+ const dest = path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
2896
3666
  await downloadFile(url, dest);
2897
3667
  logger.success(`Saved ${dest}`);
2898
3668
  idx++;
@@ -2980,7 +3750,7 @@ function requireMinimaxKey() {
2980
3750
  }
2981
3751
  return key;
2982
3752
  }
2983
- function authHeaders2(apiKey) {
3753
+ function authHeaders3(apiKey) {
2984
3754
  return {
2985
3755
  Authorization: `Bearer ${apiKey}`,
2986
3756
  "Content-Type": "application/json"
@@ -2992,7 +3762,7 @@ async function apiPost(endpoint, payload, apiKey, opts = {}) {
2992
3762
  const data = await httpJson({
2993
3763
  url,
2994
3764
  method: "POST",
2995
- headers: authHeaders2(apiKey),
3765
+ headers: authHeaders3(apiKey),
2996
3766
  body: payload,
2997
3767
  timeoutMs: opts.timeoutMs ?? 12e4
2998
3768
  });
@@ -3005,17 +3775,17 @@ async function apiPost(endpoint, payload, apiKey, opts = {}) {
3005
3775
  }
3006
3776
  return data;
3007
3777
  }
3008
- async function apiGet(endpoint, params, apiKey) {
3778
+ async function apiGet2(endpoint, params, apiKey) {
3009
3779
  const url = new URL(`${MINIMAX_BASE_URL}/${endpoint}`);
3010
3780
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
3011
- return httpJson({ url: url.toString(), headers: authHeaders2(apiKey) });
3781
+ return httpJson({ url: url.toString(), headers: authHeaders3(apiKey) });
3012
3782
  }
3013
3783
  async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
3014
3784
  const { intervalMs = 1e4, timeoutMs = 6e5, logger } = opts;
3015
3785
  const deadline = Date.now() + timeoutMs;
3016
3786
  let elapsed = 0;
3017
3787
  while (Date.now() < deadline) {
3018
- const result = await apiGet(`query/${taskType}`, { task_id: taskId }, apiKey);
3788
+ const result = await apiGet2(`query/${taskType}`, { task_id: taskId }, apiKey);
3019
3789
  const status = result.status ?? "Unknown";
3020
3790
  if (elapsed > 0 && elapsed % 3e4 < intervalMs) {
3021
3791
  logger?.debug(`Polling... ${Math.round(elapsed / 1e3)}s elapsed, status: ${status}`);
@@ -3030,7 +3800,7 @@ async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
3030
3800
  throw new ProviderError(`Task ${taskId} timed out after ${timeoutMs / 1e3}s`, "minimax");
3031
3801
  }
3032
3802
  async function downloadMinimaxFile(fileId, apiKey, dest, logger) {
3033
- const result = await apiGet(
3803
+ const result = await apiGet2(
3034
3804
  "files/retrieve",
3035
3805
  { file_id: fileId },
3036
3806
  apiKey
@@ -3049,8 +3819,8 @@ function sleep4(ms) {
3049
3819
 
3050
3820
  // src/providers/minimax/generators/music.ts
3051
3821
  init_http_client();
3052
- import fs19 from "fs";
3053
- import path24 from "path";
3822
+ import fs24 from "fs";
3823
+ import path29 from "path";
3054
3824
  async function generateMinimaxMusic(opts) {
3055
3825
  const {
3056
3826
  apiKey,
@@ -3095,14 +3865,14 @@ async function generateMinimaxMusic(opts) {
3095
3865
  }
3096
3866
  const durationMs = resp.extra_info?.music_duration ?? 0;
3097
3867
  const outDir = getOutputDir();
3098
- const dest = path24.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3868
+ const dest = path29.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3099
3869
  try {
3100
3870
  if (audioData.startsWith("http")) {
3101
3871
  const bytes = await fetchBytes(audioData);
3102
- fs19.writeFileSync(dest, bytes);
3872
+ fs24.writeFileSync(dest, bytes);
3103
3873
  } else {
3104
3874
  const audioBytes = Buffer.from(audioData, "hex");
3105
- fs19.writeFileSync(dest, audioBytes);
3875
+ fs24.writeFileSync(dest, audioBytes);
3106
3876
  }
3107
3877
  } catch (e) {
3108
3878
  return {
@@ -3113,8 +3883,8 @@ async function generateMinimaxMusic(opts) {
3113
3883
  const durSec = durationMs / 1e3;
3114
3884
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
3115
3885
  if (output) {
3116
- fs19.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3117
- fs19.copyFileSync(dest, output);
3886
+ fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3887
+ fs24.copyFileSync(dest, output);
3118
3888
  }
3119
3889
  return { status: "success", generatedAudio: dest, durationMs, model };
3120
3890
  }
@@ -3142,7 +3912,7 @@ var ALL_MINIMAX_MODELS = /* @__PURE__ */ new Set([
3142
3912
  ...MINIMAX_SPEECH_MODELS,
3143
3913
  ...MINIMAX_MUSIC_MODELS
3144
3914
  ]);
3145
- var TASK_DEFAULTS = {
3915
+ var TASK_DEFAULTS2 = {
3146
3916
  image: process.env.MINIMAX_IMAGE_MODEL ?? "image-01",
3147
3917
  video: process.env.MINIMAX_VIDEO_MODEL ?? "MiniMax-Hailuo-2.3",
3148
3918
  speech: process.env.MINIMAX_SPEECH_MODEL ?? "speech-2.8-hd",
@@ -3161,7 +3931,7 @@ function registerMinimaxGenerateMusicCommand(parent) {
3161
3931
  if (!opts.lyrics && !opts.prompt) {
3162
3932
  throw new ValidationError("Either --lyrics or --prompt is required");
3163
3933
  }
3164
- const model = opts.model ?? TASK_DEFAULTS.music;
3934
+ const model = opts.model ?? TASK_DEFAULTS2.music;
3165
3935
  const result = await generateMinimaxMusic({
3166
3936
  apiKey,
3167
3937
  lyrics: opts.lyrics,
@@ -3188,8 +3958,8 @@ Generated music: ${result.generatedAudio}`);
3188
3958
  init_errors();
3189
3959
 
3190
3960
  // src/providers/minimax/generators/speech.ts
3191
- import fs20 from "fs";
3192
- import path25 from "path";
3961
+ import fs25 from "fs";
3962
+ import path30 from "path";
3193
3963
  async function generateMinimaxSpeech(opts) {
3194
3964
  const {
3195
3965
  apiKey,
@@ -3236,12 +4006,12 @@ async function generateMinimaxSpeech(opts) {
3236
4006
  const audioBytes = Buffer.from(audioHex, "hex");
3237
4007
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
3238
4008
  const outDir = getOutputDir();
3239
- const dest = path25.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
3240
- fs20.writeFileSync(dest, audioBytes);
4009
+ const dest = path30.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4010
+ fs25.writeFileSync(dest, audioBytes);
3241
4011
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
3242
4012
  if (output) {
3243
- fs20.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
3244
- fs20.copyFileSync(dest, output);
4013
+ fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4014
+ fs25.copyFileSync(dest, output);
3245
4015
  }
3246
4016
  return { status: "success", generatedAudio: dest, model };
3247
4017
  }
@@ -3261,7 +4031,7 @@ function registerMinimaxGenerateSpeechCommand(parent) {
3261
4031
  if (!SPEECH_FORMATS.includes(opts.outputFormat)) {
3262
4032
  throw new ValidationError(`--output-format must be one of: ${SPEECH_FORMATS.join(", ")}`);
3263
4033
  }
3264
- const model = opts.model ?? TASK_DEFAULTS.speech;
4034
+ const model = opts.model ?? TASK_DEFAULTS2.speech;
3265
4035
  const rate = Number.parseFloat(opts.rate);
3266
4036
  const result = await generateMinimaxSpeech({
3267
4037
  apiKey,
@@ -3288,8 +4058,8 @@ Generated audio: ${result.generatedAudio}`);
3288
4058
  init_errors();
3289
4059
 
3290
4060
  // src/providers/minimax/generators/video.ts
3291
- import fs21 from "fs";
3292
- import path26 from "path";
4061
+ import fs26 from "fs";
4062
+ import path31 from "path";
3293
4063
  async function generateMinimaxVideo(opts) {
3294
4064
  const {
3295
4065
  apiKey,
@@ -3335,18 +4105,18 @@ async function generateMinimaxVideo(opts) {
3335
4105
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
3336
4106
  }
3337
4107
  const outDir = getOutputDir();
3338
- const dest = path26.join(outDir, `minimax_video_${Date.now()}.mp4`);
4108
+ const dest = path31.join(outDir, `minimax_video_${Date.now()}.mp4`);
3339
4109
  try {
3340
4110
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
3341
4111
  } catch (e) {
3342
4112
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
3343
4113
  }
3344
4114
  const elapsed = (Date.now() - start) / 1e3;
3345
- const stat = fs21.statSync(dest);
4115
+ const stat = fs26.statSync(dest);
3346
4116
  const fileSizeMb = stat.size / (1024 * 1024);
3347
4117
  if (output) {
3348
- fs21.mkdirSync(path26.dirname(path26.resolve(output)), { recursive: true });
3349
- fs21.copyFileSync(dest, output);
4118
+ fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4119
+ fs26.copyFileSync(dest, output);
3350
4120
  }
3351
4121
  await maybeDownloadThumb(pollResult, dest, {
3352
4122
  skip: thumb === false,
@@ -3369,7 +4139,7 @@ function registerMinimaxGenerateVideoCommand(parent) {
3369
4139
  async (opts) => {
3370
4140
  const logger = createLogger({ verbose: opts.verbose ?? false });
3371
4141
  const apiKey = requireMinimaxKey();
3372
- const model = opts.model ?? TASK_DEFAULTS.video;
4142
+ const model = opts.model ?? TASK_DEFAULTS2.video;
3373
4143
  const duration = Number.parseInt(opts.duration, 10);
3374
4144
  if (!VIDEO_DURATIONS.includes(duration)) {
3375
4145
  throw new ValidationError(`--duration must be one of: ${VIDEO_DURATIONS.join(", ")}`);
@@ -3409,8 +4179,8 @@ Generated video: ${result.generatedVideo}`);
3409
4179
  // src/providers/minimax/generators/image.ts
3410
4180
  init_errors();
3411
4181
  init_http_client();
3412
- import fs22 from "fs";
3413
- import path27 from "path";
4182
+ import fs27 from "fs";
4183
+ import path32 from "path";
3414
4184
  async function generateMinimaxImage(opts) {
3415
4185
  const {
3416
4186
  apiKey,
@@ -3448,8 +4218,8 @@ async function generateMinimaxImage(opts) {
3448
4218
  try {
3449
4219
  const bytes = await fetchBytes(url);
3450
4220
  const fname = `minimax_image_${Date.now()}_${i}.png`;
3451
- const dest = path27.join(outDir, fname);
3452
- fs22.writeFileSync(dest, bytes);
4221
+ const dest = path32.join(outDir, fname);
4222
+ fs27.writeFileSync(dest, bytes);
3453
4223
  savedFiles.push(dest);
3454
4224
  logger?.success(`Saved: ${dest}`);
3455
4225
  } catch (e) {
@@ -3460,8 +4230,8 @@ async function generateMinimaxImage(opts) {
3460
4230
  }
3461
4231
  }
3462
4232
  if (output && savedFiles[0]) {
3463
- fs22.mkdirSync(path27.dirname(path27.resolve(output)), { recursive: true });
3464
- fs22.copyFileSync(savedFiles[0], output);
4233
+ fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4234
+ fs27.copyFileSync(savedFiles[0], output);
3465
4235
  }
3466
4236
  return { status: "success", generatedImages: savedFiles, model };
3467
4237
  }
@@ -3472,7 +4242,7 @@ function registerMinimaxGenerateCommand(parent) {
3472
4242
  async (opts) => {
3473
4243
  const logger = createLogger({ verbose: opts.verbose ?? false });
3474
4244
  const apiKey = requireMinimaxKey();
3475
- const model = opts.model ?? TASK_DEFAULTS.image;
4245
+ const model = opts.model ?? TASK_DEFAULTS2.image;
3476
4246
  const numImages = Math.min(Math.max(Number.parseInt(opts.numImages, 10) || 1, 1), 9);
3477
4247
  const result = await generateMinimaxImage({
3478
4248
  apiKey,
@@ -3497,8 +4267,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
3497
4267
  // src/providers/minimax/commands/image-to-image.ts
3498
4268
  init_errors();
3499
4269
  init_http_client();
3500
- import fs23 from "fs";
3501
- import path28 from "path";
4270
+ import fs28 from "fs";
4271
+ import path33 from "path";
3502
4272
  function registerMinimaxImageToImageCommand(parent) {
3503
4273
  parent.command("image-to-image").alias("i2i").description(
3504
4274
  "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."
@@ -3509,7 +4279,7 @@ function registerMinimaxImageToImageCommand(parent) {
3509
4279
  async (opts) => {
3510
4280
  const logger = createLogger({ verbose: opts.verbose ?? false });
3511
4281
  const apiKey = requireMinimaxKey();
3512
- const model = opts.model ?? TASK_DEFAULTS.image;
4282
+ const model = opts.model ?? TASK_DEFAULTS2.image;
3513
4283
  const numImages = Math.min(Math.max(Number.parseInt(opts.numImages, 10) || 1, 1), 9);
3514
4284
  logger.warn(
3515
4285
  "MiniMax i2i preserves a CHARACTER reference only. It does not edit the input image. If you need free-form edits, use byteplus, gemini, openrouter, or leonardo image-to-image."
@@ -3553,8 +4323,8 @@ function registerMinimaxImageToImageCommand(parent) {
3553
4323
  if (!url) continue;
3554
4324
  try {
3555
4325
  const bytes = await fetchBytes(url);
3556
- const dest = path28.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
3557
- fs23.writeFileSync(dest, bytes);
4326
+ const dest = path33.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4327
+ fs28.writeFileSync(dest, bytes);
3558
4328
  saved.push(dest);
3559
4329
  logger.success(`Saved: ${dest}`);
3560
4330
  } catch (e) {
@@ -3565,8 +4335,8 @@ function registerMinimaxImageToImageCommand(parent) {
3565
4335
  }
3566
4336
  }
3567
4337
  if (opts.output && saved[0]) {
3568
- fs23.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
3569
- fs23.copyFileSync(saved[0], opts.output);
4338
+ fs28.mkdirSync(path33.dirname(path33.resolve(opts.output)), { recursive: true });
4339
+ fs28.copyFileSync(saved[0], opts.output);
3570
4340
  logger.success(`Copied to: ${opts.output}`);
3571
4341
  }
3572
4342
  console.log(`
@@ -3587,8 +4357,8 @@ function registerMinimaxCommands(program2) {
3587
4357
  }
3588
4358
 
3589
4359
  // src/providers/openrouter/client.ts
3590
- import fs24 from "fs";
3591
- import path29 from "path";
4360
+ import fs29 from "fs";
4361
+ import path34 from "path";
3592
4362
  init_errors();
3593
4363
  init_http_client();
3594
4364
  var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
@@ -3677,8 +4447,8 @@ async function generateOpenRouterImage(opts) {
3677
4447
  if (!url) continue;
3678
4448
  try {
3679
4449
  const bytes = await fetchBytes(url);
3680
- const dest = path29.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
3681
- fs24.writeFileSync(dest, bytes);
4450
+ const dest = path34.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
4451
+ fs29.writeFileSync(dest, bytes);
3682
4452
  savedFiles.push(dest);
3683
4453
  logger?.success(`Saved: ${dest}`);
3684
4454
  } catch (e) {
@@ -3689,8 +4459,8 @@ async function generateOpenRouterImage(opts) {
3689
4459
  }
3690
4460
  }
3691
4461
  if (output && savedFiles[0]) {
3692
- fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3693
- fs24.writeFileSync(output, fs24.readFileSync(savedFiles[0]));
4462
+ fs29.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
4463
+ fs29.writeFileSync(output, fs29.readFileSync(savedFiles[0]));
3694
4464
  }
3695
4465
  return { status: "success", generatedImages: savedFiles, model: usedModel };
3696
4466
  }
@@ -3725,8 +4495,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
3725
4495
  }
3726
4496
 
3727
4497
  // src/providers/openrouter/commands/image-to-image.ts
3728
- import fs25 from "fs";
3729
- import path30 from "path";
4498
+ import fs30 from "fs";
4499
+ import path35 from "path";
3730
4500
  init_errors();
3731
4501
  init_http_client();
3732
4502
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
@@ -3813,14 +4583,14 @@ async function runImageToImage(opts) {
3813
4583
  const url = messageImages[i]?.image_url?.url;
3814
4584
  if (!url) continue;
3815
4585
  const bytes = await fetchBytes(url);
3816
- const dest = path30.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
3817
- fs25.writeFileSync(dest, bytes);
4586
+ const dest = path35.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
4587
+ fs30.writeFileSync(dest, bytes);
3818
4588
  saved.push(dest);
3819
4589
  opts.logger.success(`Saved: ${dest}`);
3820
4590
  }
3821
4591
  if (opts.output && saved[0]) {
3822
- fs25.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
3823
- fs25.copyFileSync(saved[0], opts.output);
4592
+ fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4593
+ fs30.copyFileSync(saved[0], opts.output);
3824
4594
  opts.logger.success(`Copied to: ${opts.output}`);
3825
4595
  }
3826
4596
  return saved;
@@ -3829,7 +4599,7 @@ async function runImageToImage(opts) {
3829
4599
  // src/providers/openrouter/video-client.ts
3830
4600
  init_http_client();
3831
4601
  var OPENROUTER_VIDEOS_URL = "https://openrouter.ai/api/v1/videos";
3832
- function authHeaders3(apiKey) {
4602
+ function authHeaders4(apiKey) {
3833
4603
  const headers = {
3834
4604
  Authorization: `Bearer ${apiKey}`,
3835
4605
  "Content-Type": "application/json"
@@ -3844,7 +4614,7 @@ async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
3844
4614
  return httpJson({
3845
4615
  url: OPENROUTER_VIDEOS_URL,
3846
4616
  method: "POST",
3847
- headers: authHeaders3(apiKey),
4617
+ headers: authHeaders4(apiKey),
3848
4618
  body,
3849
4619
  timeoutMs: 12e4
3850
4620
  });
@@ -3852,7 +4622,7 @@ async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
3852
4622
  async function pollVideoJob(jobId, apiKey = requireOpenRouterKey()) {
3853
4623
  return httpJson({
3854
4624
  url: `${OPENROUTER_VIDEOS_URL}/${jobId}`,
3855
- headers: authHeaders3(apiKey)
4625
+ headers: authHeaders4(apiKey)
3856
4626
  });
3857
4627
  }
3858
4628
  function videoContentUrl(jobId) {
@@ -3861,18 +4631,18 @@ function videoContentUrl(jobId) {
3861
4631
  async function listVideoModels(apiKey = requireOpenRouterKey()) {
3862
4632
  return httpJson({
3863
4633
  url: `${OPENROUTER_VIDEOS_URL}/models`,
3864
- headers: authHeaders3(apiKey)
4634
+ headers: authHeaders4(apiKey)
3865
4635
  });
3866
4636
  }
3867
4637
 
3868
4638
  // src/providers/openrouter/commands/video-status.ts
3869
- import fs26 from "fs";
3870
- import path31 from "path";
4639
+ import fs31 from "fs";
4640
+ import path36 from "path";
3871
4641
  async function downloadOpenRouterVideo(opts) {
3872
4642
  const { jobId, logger, source } = opts;
3873
4643
  const apiKey = requireOpenRouterKey();
3874
4644
  const url = videoContentUrl(jobId);
3875
- const dest = path31.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
4645
+ const dest = path36.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
3876
4646
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
3877
4647
  if (!r.ok) {
3878
4648
  logger.error(`Download HTTP ${r.status}`);
@@ -3882,14 +4652,14 @@ async function downloadOpenRouterVideo(opts) {
3882
4652
  logger.error("Empty response body");
3883
4653
  process.exit(1);
3884
4654
  }
3885
- fs26.mkdirSync(path31.dirname(dest), { recursive: true });
3886
- const ws = fs26.createWriteStream(dest);
4655
+ fs31.mkdirSync(path36.dirname(dest), { recursive: true });
4656
+ const ws = fs31.createWriteStream(dest);
3887
4657
  const { pipeline: pipeline2 } = await import("stream/promises");
3888
4658
  await pipeline2(r.body, ws);
3889
4659
  logger.success(`Saved ${dest}`);
3890
4660
  if (opts.output) {
3891
- fs26.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
3892
- fs26.copyFileSync(dest, opts.output);
4661
+ fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
4662
+ fs31.copyFileSync(dest, opts.output);
3893
4663
  logger.success(`Copied to ${opts.output}`);
3894
4664
  }
3895
4665
  await maybeDownloadThumb(source, dest, {
@@ -4151,6 +4921,12 @@ var PROVIDERS = [
4151
4921
  envFallback: "ARK_API_KEY",
4152
4922
  optional: true,
4153
4923
  link: "https://console.byteplus.com/auth/api-keys"
4924
+ },
4925
+ {
4926
+ name: "ElevenLabs",
4927
+ envPrimary: "ELEVENLABS_API_KEY",
4928
+ optional: true,
4929
+ link: "https://elevenlabs.io/app/settings/api-keys"
4154
4930
  }
4155
4931
  ];
4156
4932
  function resolveProviderKey(p) {
@@ -4237,8 +5013,8 @@ function registerCheckCommand(program2) {
4237
5013
 
4238
5014
  // src/commands/doc/convert.ts
4239
5015
  init_errors();
4240
- import fs27 from "fs";
4241
- import path32 from "path";
5016
+ import fs32 from "fs";
5017
+ import path37 from "path";
4242
5018
 
4243
5019
  // src/commands/doc/default-prompt.ts
4244
5020
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -4259,18 +5035,18 @@ async function convertDocuments(opts) {
4259
5035
  const model = opts.model ?? DOC_MODEL_DEFAULT;
4260
5036
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
4261
5037
  for (const f of inputs) {
4262
- if (!fs27.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
5038
+ if (!fs32.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
4263
5039
  }
4264
5040
  let outputPath;
4265
5041
  if (output) {
4266
- outputPath = path32.resolve(output);
5042
+ outputPath = path37.resolve(output);
4267
5043
  } else if (autoName && inputs.length === 1 && inputs[0]) {
4268
- const base = path32.basename(inputs[0], path32.extname(inputs[0]));
4269
- outputPath = path32.join(getOutputDir(), `${base}-extraction.md`);
5044
+ const base = path37.basename(inputs[0], path37.extname(inputs[0]));
5045
+ outputPath = path37.join(getOutputDir(), `${base}-extraction.md`);
4270
5046
  } else {
4271
- outputPath = path32.join(getOutputDir(), "document-extraction.md");
5047
+ outputPath = path37.join(getOutputDir(), "document-extraction.md");
4272
5048
  }
4273
- fs27.mkdirSync(path32.dirname(outputPath), { recursive: true });
5049
+ fs32.mkdirSync(path37.dirname(outputPath), { recursive: true });
4274
5050
  const results = [];
4275
5051
  for (let i = 0; i < inputs.length; i++) {
4276
5052
  const filePath = inputs[i];
@@ -4321,7 +5097,7 @@ async function convertDocuments(opts) {
4321
5097
  `);
4322
5098
  for (const r of results) {
4323
5099
  lines.push(`
4324
- ## ${path32.basename(r.file)}
5100
+ ## ${path37.basename(r.file)}
4325
5101
  `);
4326
5102
  if (r.status === "success" && r.markdown) {
4327
5103
  lines.push(`${r.markdown}
@@ -4334,7 +5110,7 @@ async function convertDocuments(opts) {
4334
5110
  }
4335
5111
  lines.push("\n---\n");
4336
5112
  }
4337
- fs27.writeFileSync(outputPath, lines.join("\n"), "utf8");
5113
+ fs32.writeFileSync(outputPath, lines.join("\n"), "utf8");
4338
5114
  const succeeded = results.filter((r) => r.status === "success").length;
4339
5115
  console.log(`
4340
5116
  ${"=".repeat(50)}`);
@@ -4380,12 +5156,12 @@ function registerDocCommands(program2) {
4380
5156
 
4381
5157
  // src/commands/media/index.ts
4382
5158
  init_errors();
4383
- import fs30 from "fs";
4384
- import path35 from "path";
5159
+ import fs35 from "fs";
5160
+ import path40 from "path";
4385
5161
 
4386
5162
  // src/commands/media/batch.ts
4387
- import fs28 from "fs";
4388
- import path33 from "path";
5163
+ import fs33 from "fs";
5164
+ import path38 from "path";
4389
5165
 
4390
5166
  // src/commands/media/detect-type.ts
4391
5167
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -4581,10 +5357,10 @@ async function batchOptimize(opts) {
4581
5357
  verbose,
4582
5358
  logger
4583
5359
  } = opts;
4584
- fs28.mkdirSync(outputDir, { recursive: true });
4585
- const entries = fs28.readdirSync(inputDir);
5360
+ fs33.mkdirSync(outputDir, { recursive: true });
5361
+ const entries = fs33.readdirSync(inputDir);
4586
5362
  const mediaFiles = entries.filter((f) => {
4587
- const ext = path33.extname(f).toLowerCase();
5363
+ const ext = path38.extname(f).toLowerCase();
4588
5364
  return ALL_EXTS.has(ext);
4589
5365
  });
4590
5366
  if (mediaFiles.length === 0) {
@@ -4595,9 +5371,9 @@ async function batchOptimize(opts) {
4595
5371
  let succeeded = 0;
4596
5372
  const errors = [];
4597
5373
  for (const file of mediaFiles) {
4598
- const inputPath = path33.join(inputDir, file);
4599
- const outputPath = path33.join(outputDir, file);
4600
- const ext = path33.extname(file).toLowerCase();
5374
+ const inputPath = path38.join(inputDir, file);
5375
+ const outputPath = path38.join(outputDir, file);
5376
+ const ext = path38.extname(file).toLowerCase();
4601
5377
  const kind = extToKind(ext);
4602
5378
  logger?.debug(`Processing: ${file}`);
4603
5379
  try {
@@ -4634,8 +5410,8 @@ async function batchOptimize(opts) {
4634
5410
 
4635
5411
  // src/commands/media/split-video.ts
4636
5412
  init_errors();
4637
- import fs29 from "fs";
4638
- import path34 from "path";
5413
+ import fs34 from "fs";
5414
+ import path39 from "path";
4639
5415
  async function splitVideo(opts) {
4640
5416
  await assertFfmpeg();
4641
5417
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -4646,10 +5422,10 @@ async function splitVideo(opts) {
4646
5422
  logger?.info("Video is short enough \u2014 no splitting needed");
4647
5423
  return [input];
4648
5424
  }
4649
- fs29.mkdirSync(outputDir, { recursive: true });
4650
- const ext = path34.extname(input) || ".mp4";
4651
- const baseName = path34.basename(input, ext);
4652
- const outputPattern = path34.join(outputDir, `${baseName}_chunk_%03d${ext}`);
5425
+ fs34.mkdirSync(outputDir, { recursive: true });
5426
+ const ext = path39.extname(input) || ".mp4";
5427
+ const baseName = path39.basename(input, ext);
5428
+ const outputPattern = path39.join(outputDir, `${baseName}_chunk_%03d${ext}`);
4653
5429
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
4654
5430
  const args = [
4655
5431
  "-i",
@@ -4668,8 +5444,8 @@ async function splitVideo(opts) {
4668
5444
  await runFfmpeg(args, { verbose, logger });
4669
5445
  const files = [];
4670
5446
  for (let i = 0; i < numChunks + 1; i++) {
4671
- const fname = path34.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
4672
- if (fs29.existsSync(fname)) files.push(fname);
5447
+ const fname = path39.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
5448
+ if (fs34.existsSync(fname)) files.push(fname);
4673
5449
  }
4674
5450
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
4675
5451
  return files;
@@ -4683,9 +5459,9 @@ function registerMediaCommands(program2) {
4683
5459
  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(
4684
5460
  async (opts) => {
4685
5461
  const logger = createLogger({ verbose: opts.verbose ?? false });
4686
- if (!fs30.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
4687
- fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4688
- const ext = path35.extname(opts.input).toLowerCase();
5462
+ if (!fs35.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
5463
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5464
+ const ext = path40.extname(opts.input).toLowerCase();
4689
5465
  const kind = extToKind(ext);
4690
5466
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
4691
5467
  const quality = Number.parseInt(opts.quality, 10);
@@ -4725,7 +5501,7 @@ function registerMediaCommands(program2) {
4725
5501
  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(
4726
5502
  async (opts) => {
4727
5503
  const logger = createLogger({ verbose: opts.verbose ?? false });
4728
- if (!fs30.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
5504
+ if (!fs35.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
4729
5505
  const chunks = await splitVideo({
4730
5506
  input: opts.input,
4731
5507
  outputDir: opts.outputDir,
@@ -4741,7 +5517,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
4741
5517
  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(
4742
5518
  async (opts) => {
4743
5519
  const logger = createLogger({ verbose: opts.verbose ?? false });
4744
- if (!fs30.existsSync(opts.inputDir))
5520
+ if (!fs35.existsSync(opts.inputDir))
4745
5521
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
4746
5522
  const result = await batchOptimize({
4747
5523
  inputDir: opts.inputDir,
@@ -4770,6 +5546,7 @@ function registerCommands(program2) {
4770
5546
  registerOpenRouterCommands(program2);
4771
5547
  registerLeonardoCommands(program2);
4772
5548
  registerBytePlusCommands(program2);
5549
+ registerElevenLabsCommands(program2);
4773
5550
  registerMediaCommands(program2);
4774
5551
  registerDocCommands(program2);
4775
5552
  }