@mrgoonie/multix 0.0.5 → 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.
Files changed (4) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +91 -1
  3. package/dist/cli.js +1400 -484
  4. package/package.json +9 -3
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"() {
@@ -67,8 +75,8 @@ __export(http_client_exports, {
67
75
  fetchBytes: () => fetchBytes,
68
76
  httpJson: () => httpJson
69
77
  });
70
- import fs2 from "fs";
71
- import path2 from "path";
78
+ import fs4 from "fs";
79
+ import path4 from "path";
72
80
  import { pipeline } from "stream/promises";
73
81
  async function httpJson(opts) {
74
82
  const { url, method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
@@ -103,7 +111,7 @@ async function httpJson(opts) {
103
111
  return response.json();
104
112
  }
105
113
  async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
106
- fs2.mkdirSync(path2.dirname(dest), { recursive: true });
114
+ fs4.mkdirSync(path4.dirname(dest), { recursive: true });
107
115
  let response;
108
116
  try {
109
117
  response = await globalThis.fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
@@ -117,7 +125,7 @@ async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
117
125
  if (!response.body) {
118
126
  throw new HttpError(0, "Response body is null", url);
119
127
  }
120
- const fileStream = fs2.createWriteStream(dest);
128
+ const fileStream = fs4.createWriteStream(dest);
121
129
  await pipeline(response.body, fileStream);
122
130
  }
123
131
  async function fetchBytes(urlOrDataUri) {
@@ -155,6 +163,90 @@ var init_http_client = __esm({
155
163
  import { createRequire } from "module";
156
164
  import { Command } from "commander";
157
165
 
166
+ // src/providers/byteplus/commands/generate-3d.ts
167
+ import path5 from "path";
168
+
169
+ // src/core/image-input.ts
170
+ import fs from "fs/promises";
171
+ import path from "path";
172
+ var IMAGE_MIME_BY_EXT = {
173
+ ".jpg": "image/jpeg",
174
+ ".jpeg": "image/jpeg",
175
+ ".png": "image/png",
176
+ ".webp": "image/webp",
177
+ ".gif": "image/gif"
178
+ };
179
+ var VIDEO_MIME_BY_EXT = {
180
+ ".mp4": "video/mp4",
181
+ ".webm": "video/webm",
182
+ ".mov": "video/quicktime",
183
+ ".m4v": "video/x-m4v"
184
+ };
185
+ var AUDIO_MIME_BY_EXT = {
186
+ ".mp3": "audio/mpeg",
187
+ ".wav": "audio/wav",
188
+ ".m4a": "audio/mp4",
189
+ ".flac": "audio/flac",
190
+ ".ogg": "audio/ogg"
191
+ };
192
+ var DEFAULT_SOFT_LIMIT_BYTES = 10 * 1024 * 1024;
193
+ var DEFAULT_HARD_LIMIT_BYTES = 25 * 1024 * 1024;
194
+ function isHttpUrl(input) {
195
+ return /^https?:\/\//i.test(input);
196
+ }
197
+ async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
198
+ const ext = path.extname(filePath).toLowerCase();
199
+ const mime = mimeTable[ext];
200
+ if (!mime) {
201
+ throw new Error(
202
+ `Unsupported ${kindLabel} extension '${ext}'. Allowed: ${Object.keys(mimeTable).join(", ")}`
203
+ );
204
+ }
205
+ const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
206
+ const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
207
+ const buf = await fs.readFile(filePath);
208
+ if (buf.byteLength > hard) {
209
+ throw new Error(
210
+ `${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
211
+ );
212
+ }
213
+ if (buf.byteLength > soft) {
214
+ opts.logger?.warn(
215
+ `${kindLabel} payload ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB is large; consider URL input.`
216
+ );
217
+ }
218
+ const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
219
+ return { dataUrl, mime, bytes: buf.byteLength };
220
+ }
221
+ async function resolveImageInput(input, opts = {}) {
222
+ const o = normalizeOpts(opts);
223
+ if (isHttpUrl(input)) return { kind: "url", url: input };
224
+ const { dataUrl, mime, bytes } = await readAsDataUrl(input, IMAGE_MIME_BY_EXT, "image", o);
225
+ return { kind: "data", dataUrl, mime, bytes };
226
+ }
227
+ async function resolveVideoInput(input, opts = {}) {
228
+ const o = normalizeOpts(opts);
229
+ if (isHttpUrl(input)) return { kind: "url", url: input };
230
+ const { dataUrl, mime, bytes } = await readAsDataUrl(input, VIDEO_MIME_BY_EXT, "video", o);
231
+ return { kind: "data", dataUrl, mime, bytes };
232
+ }
233
+ async function resolveAudioInput(input, opts = {}) {
234
+ const o = normalizeOpts(opts);
235
+ if (isHttpUrl(input)) return { kind: "url", url: input };
236
+ const { dataUrl, mime, bytes } = await readAsDataUrl(input, AUDIO_MIME_BY_EXT, "audio", o);
237
+ return { kind: "data", dataUrl, mime, bytes };
238
+ }
239
+ function refUrl(r) {
240
+ return r.kind === "url" ? r.url : r.dataUrl;
241
+ }
242
+ function normalizeOpts(opts) {
243
+ if (!opts) return {};
244
+ if (typeof opts.info === "function" && typeof opts.warn === "function") {
245
+ return { logger: opts };
246
+ }
247
+ return opts;
248
+ }
249
+
158
250
  // src/core/logger.ts
159
251
  var ANSI = {
160
252
  reset: "\x1B[0m",
@@ -200,22 +292,69 @@ ${line}
200
292
  }
201
293
  var defaultLogger = createLogger({ verbose: false });
202
294
 
295
+ // src/core/output-dir.ts
296
+ import fs2 from "fs";
297
+ import path2 from "path";
298
+ var cachedOutputDir;
299
+ function getOutputDir() {
300
+ if (cachedOutputDir) return cachedOutputDir;
301
+ const dir = process.env.MULTIX_OUTPUT_DIR ? path2.resolve(process.env.MULTIX_OUTPUT_DIR) : path2.resolve(process.cwd(), "multix-output");
302
+ fs2.mkdirSync(dir, { recursive: true });
303
+ cachedOutputDir = dir;
304
+ return dir;
305
+ }
306
+
307
+ // src/providers/leonardo/poll.ts
308
+ var PollTimeoutError = class extends Error {
309
+ constructor() {
310
+ super("Polling timed out before the operation completed.");
311
+ this.name = "PollTimeoutError";
312
+ }
313
+ };
314
+ var PollFailedError = class extends Error {
315
+ constructor(value) {
316
+ super("Operation failed.");
317
+ this.value = value;
318
+ this.name = "PollFailedError";
319
+ }
320
+ value;
321
+ };
322
+ async function poll(opts) {
323
+ const interval = opts.intervalMs ?? 4e3;
324
+ const max = opts.maxAttempts ?? 120;
325
+ for (let attempt = 1; attempt <= max; attempt++) {
326
+ if (opts.signal?.aborted) throw new Error("Aborted");
327
+ const value = await opts.fetch();
328
+ opts.onTick?.(attempt, value);
329
+ if (opts.failed?.(value)) throw new PollFailedError(value);
330
+ if (opts.done(value)) return value;
331
+ await new Promise((resolve, reject) => {
332
+ const t = setTimeout(resolve, interval);
333
+ opts.signal?.addEventListener("abort", () => {
334
+ clearTimeout(t);
335
+ reject(new Error("Aborted"));
336
+ });
337
+ });
338
+ }
339
+ throw new PollTimeoutError();
340
+ }
341
+
203
342
  // src/core/env-loader.ts
204
- import fs from "fs";
343
+ import fs3 from "fs";
205
344
  import os from "os";
206
- import path from "path";
345
+ import path3 from "path";
207
346
  import { config as dotenvConfig } from "dotenv";
208
347
  var loaded = false;
209
348
  function loadEnv() {
210
349
  if (loaded) return;
211
350
  loaded = true;
212
- const cwdEnv = path.resolve(process.cwd(), ".env");
213
- if (fs.existsSync(cwdEnv)) {
351
+ const cwdEnv = path3.resolve(process.cwd(), ".env");
352
+ if (fs3.existsSync(cwdEnv)) {
214
353
  dotenvConfig({ path: cwdEnv, override: false });
215
354
  }
216
355
  if (!process.env.MULTIX_DISABLE_HOME_ENV) {
217
- const homeEnv = path.join(os.homedir(), ".multix", ".env");
218
- if (fs.existsSync(homeEnv)) {
356
+ const homeEnv = path3.join(os.homedir(), ".multix", ".env");
357
+ if (fs3.existsSync(homeEnv)) {
219
358
  dotenvConfig({ path: homeEnv, override: false });
220
359
  }
221
360
  }
@@ -236,9 +375,15 @@ init_http_client();
236
375
  var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
237
376
  var DEFAULT_BYTEPLUS_IMAGE_MODEL = "seedream-4-0-250828";
238
377
  var DEFAULT_BYTEPLUS_VIDEO_MODEL = "seedance-2.0";
378
+ var DEFAULT_BYTEPLUS_3D_MODEL = "hyper3d-gen2-260112";
379
+ var BYTEPLUS_3D_MODELS = [
380
+ { id: "hyper3d-gen2-260112", name: "Hyper3D Gen2", family: "hyper3d" },
381
+ { id: "hitem3d-2-0-251223", name: "Hitem3d 2.0", family: "hitem3d" }
382
+ ];
239
383
  var BYTEPLUS_DEFAULTS = {
240
384
  imageModel: process.env.BYTEPLUS_IMAGE_MODEL ?? DEFAULT_BYTEPLUS_IMAGE_MODEL,
241
385
  videoModel: process.env.BYTEPLUS_VIDEO_MODEL ?? DEFAULT_BYTEPLUS_VIDEO_MODEL,
386
+ threeDModel: process.env.BYTEPLUS_3D_MODEL ?? DEFAULT_BYTEPLUS_3D_MODEL,
242
387
  videoResolution: "1080p",
243
388
  videoDuration: 8,
244
389
  videoAspectRatio: "16:9",
@@ -269,9 +414,9 @@ var BytePlusClient = class {
269
414
  }
270
415
  apiKey;
271
416
  baseUrl;
272
- async request(path35, opts = {}) {
417
+ async request(path41, opts = {}) {
273
418
  const base = this.baseUrl.replace(/\/$/, "");
274
- const url = new URL(base + path35);
419
+ const url = new URL(base + path41);
275
420
  if (opts.query) {
276
421
  for (const [k, v] of Object.entries(opts.query)) {
277
422
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -289,141 +434,167 @@ var BytePlusClient = class {
289
434
  timeoutMs: opts.timeoutMs ?? 12e4
290
435
  });
291
436
  }
292
- get(path35, query, logger) {
293
- return this.request(path35, { method: "GET", query, logger });
437
+ get(path41, query, logger) {
438
+ return this.request(path41, { method: "GET", query, logger });
294
439
  }
295
- post(path35, body, logger) {
296
- return this.request(path35, { method: "POST", body, logger });
440
+ post(path41, body, logger) {
441
+ return this.request(path41, { method: "POST", body, logger });
297
442
  }
298
- delete(path35, logger) {
299
- return this.request(path35, { method: "DELETE", logger });
443
+ delete(path41, logger) {
444
+ return this.request(path41, { method: "DELETE", logger });
300
445
  }
301
446
  };
302
447
  function createBytePlusClient() {
303
448
  return new BytePlusClient(requireBytePlusKey(), bytePlusBaseUrl());
304
449
  }
305
450
 
306
- // src/providers/byteplus/generators/image.ts
451
+ // src/providers/byteplus/generators/three-d.ts
307
452
  init_http_client();
308
- import fs5 from "fs";
309
- import path5 from "path";
310
-
311
- // src/core/image-input.ts
312
- import fs3 from "fs/promises";
313
- import path3 from "path";
314
- var IMAGE_MIME_BY_EXT = {
315
- ".jpg": "image/jpeg",
316
- ".jpeg": "image/jpeg",
317
- ".png": "image/png",
318
- ".webp": "image/webp",
319
- ".gif": "image/gif"
320
- };
321
- var VIDEO_MIME_BY_EXT = {
322
- ".mp4": "video/mp4",
323
- ".webm": "video/webm",
324
- ".mov": "video/quicktime",
325
- ".m4v": "video/x-m4v"
326
- };
327
- var AUDIO_MIME_BY_EXT = {
328
- ".mp3": "audio/mpeg",
329
- ".wav": "audio/wav",
330
- ".m4a": "audio/mp4",
331
- ".flac": "audio/flac",
332
- ".ogg": "audio/ogg"
333
- };
334
- var DEFAULT_SOFT_LIMIT_BYTES = 10 * 1024 * 1024;
335
- var DEFAULT_HARD_LIMIT_BYTES = 25 * 1024 * 1024;
336
- function isHttpUrl(input) {
337
- return /^https?:\/\//i.test(input);
338
- }
339
- async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
340
- const ext = path3.extname(filePath).toLowerCase();
341
- const mime = mimeTable[ext];
342
- if (!mime) {
343
- throw new Error(
344
- `Unsupported ${kindLabel} extension '${ext}'. Allowed: ${Object.keys(mimeTable).join(", ")}`
345
- );
346
- }
347
- const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
348
- const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
349
- const buf = await fs3.readFile(filePath);
350
- if (buf.byteLength > hard) {
351
- throw new Error(
352
- `${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
353
- );
453
+ function buildThreeDTaskBody(opts) {
454
+ const promptText = (opts.prompt ?? "").trim();
455
+ const flagPart = opts.flags?.trim() ?? "";
456
+ const text = [promptText, flagPart].filter((s) => s.length > 0).join(" ");
457
+ const content = [];
458
+ if (text.length > 0) content.push({ type: "text", text });
459
+ for (const img of opts.imageInputs ?? []) {
460
+ content.push({ type: "image_url", image_url: { url: refUrl(img) } });
354
461
  }
355
- if (buf.byteLength > soft) {
356
- opts.logger?.warn(
357
- `${kindLabel} payload ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB is large; consider URL input.`
358
- );
462
+ if (content.length === 0) {
463
+ throw new Error("3D task requires either a prompt or at least one image input.");
359
464
  }
360
- const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
361
- return { dataUrl, mime, bytes: buf.byteLength };
362
- }
363
- async function resolveImageInput(input, opts = {}) {
364
- const o = normalizeOpts(opts);
365
- if (isHttpUrl(input)) return { kind: "url", url: input };
366
- const { dataUrl, mime, bytes } = await readAsDataUrl(input, IMAGE_MIME_BY_EXT, "image", o);
367
- return { kind: "data", dataUrl, mime, bytes };
465
+ const body = { model: opts.model, content };
466
+ if (opts.seed !== void 0) body.seed = opts.seed;
467
+ return body;
368
468
  }
369
- async function resolveVideoInput(input, opts = {}) {
370
- const o = normalizeOpts(opts);
371
- if (isHttpUrl(input)) return { kind: "url", url: input };
372
- const { dataUrl, mime, bytes } = await readAsDataUrl(input, VIDEO_MIME_BY_EXT, "video", o);
373
- return { kind: "data", dataUrl, mime, bytes };
469
+ async function submitThreeDTask(client, body, logger) {
470
+ return client.post("/contents/generations/tasks", body, logger);
374
471
  }
375
- async function resolveAudioInput(input, opts = {}) {
376
- const o = normalizeOpts(opts);
377
- if (isHttpUrl(input)) return { kind: "url", url: input };
378
- const { dataUrl, mime, bytes } = await readAsDataUrl(input, AUDIO_MIME_BY_EXT, "audio", o);
379
- return { kind: "data", dataUrl, mime, bytes };
472
+ async function getThreeDTaskStatus(client, id, logger) {
473
+ return client.get(`/contents/generations/tasks/${id}`, void 0, logger);
380
474
  }
381
- function refUrl(r) {
382
- return r.kind === "url" ? r.url : r.dataUrl;
475
+ async function waitForThreeDTask(client, id, opts = {}) {
476
+ const intervalMs = opts.intervalMs ?? 4e3;
477
+ const maxAttempts = opts.maxAttempts ?? (opts.waitTimeoutMs ? Math.max(1, Math.ceil(opts.waitTimeoutMs / intervalMs)) : 180);
478
+ return poll({
479
+ fetch: () => getThreeDTaskStatus(client, id, opts.logger),
480
+ done: (v) => v.status === "succeeded",
481
+ failed: (v) => v.status === "failed" || v.status === "cancelled",
482
+ intervalMs,
483
+ maxAttempts,
484
+ onTick: (attempt, v) => opts.logger?.debug(`attempt ${attempt} \u2014 status: ${v.status}`)
485
+ });
383
486
  }
384
- function normalizeOpts(opts) {
385
- if (!opts) return {};
386
- if (typeof opts.info === "function" && typeof opts.warn === "function") {
387
- return { logger: opts };
388
- }
389
- return opts;
487
+ function inferModelFileExt(url) {
488
+ const m = url.match(/\.(glb|gltf|obj|fbx|usdz|stl|ply|zip)(?:\?|#|$)/i);
489
+ return m?.[1] ? m[1].toLowerCase() : "glb";
390
490
  }
391
-
392
- // src/core/output-dir.ts
393
- import fs4 from "fs";
394
- import path4 from "path";
395
- var cachedOutputDir;
396
- function getOutputDir() {
397
- if (cachedOutputDir) return cachedOutputDir;
398
- const dir = process.env.MULTIX_OUTPUT_DIR ? path4.resolve(process.env.MULTIX_OUTPUT_DIR) : path4.resolve(process.cwd(), "multix-output");
399
- fs4.mkdirSync(dir, { recursive: true });
400
- cachedOutputDir = dir;
401
- return dir;
491
+ async function downloadModelFile(url, outputPath, logger) {
492
+ await downloadFile(url, outputPath);
493
+ logger?.success(`Saved ${outputPath}`);
402
494
  }
403
495
 
404
- // src/providers/byteplus/generators/image.ts
405
- var ASPECT_TO_SIZE = {
406
- "1:1": "2048x2048",
407
- "16:9": "2560x1440",
408
- "9:16": "1440x2560",
409
- "4:3": "2048x1536",
410
- "3:4": "1536x2048",
411
- "21:9": "2560x1080",
412
- "3:2": "2048x1365",
413
- "2:3": "1365x2048"
414
- };
415
- function resolveSize(opts) {
416
- if (opts.size && opts.aspectRatio) {
417
- opts.logger?.warn("Both --size and --aspect-ratio given; using --size.");
418
- return opts.size;
419
- }
420
- if (opts.size) return opts.size;
421
- if (opts.aspectRatio) {
422
- const mapped = ASPECT_TO_SIZE[opts.aspectRatio];
423
- if (!mapped) {
424
- throw new Error(
425
- `Unsupported --aspect-ratio '${opts.aspectRatio}'. Supported: ${Object.keys(ASPECT_TO_SIZE).join(", ")}`
426
- );
496
+ // src/providers/byteplus/commands/generate-3d.ts
497
+ function registerBytePlusGenerate3DCommand(parent) {
498
+ parent.command("generate-3d").alias("3d").description(
499
+ `Generate a 3D model via BytePlus (Hyper3D / Hitem3d). Models: ${BYTEPLUS_3D_MODELS.map((m) => m.id).join(", ")}`
500
+ ).option("--prompt <text>", "Text prompt (text-to-3D or to guide image-to-3D)").option(
501
+ "-m, --model <id>",
502
+ `Model id (default ${BYTEPLUS_DEFAULTS.threeDModel})`,
503
+ BYTEPLUS_DEFAULTS.threeDModel
504
+ ).option("--input-image <path|url...>", "1\u20135 reference images for image-to-3D (path or URL)").option(
505
+ "--flags <raw>",
506
+ "Raw flags appended to the prompt (e.g. '--mesh_mode Raw --hd_texture true' for Hyper3D, '--ff 2 --resolution 1536pro' for Hitem3d)"
507
+ ).option("--seed <n>", "Fixed seed").option("--async", "Submit task and exit; print taskId only").option("--wait-timeout <ms>", "Poll timeout in ms", "900000").option("--output <path>", "Output file path (extension auto-detected from URL if omitted)").option("-v, --verbose", "Verbose logging").action(
508
+ async (opts) => {
509
+ const logger = createLogger({ verbose: opts.verbose ?? false });
510
+ const client = createBytePlusClient();
511
+ const images = opts.inputImage ?? [];
512
+ if (images.length > 5) {
513
+ logger.error("BytePlus 3D supports at most 5 reference images.");
514
+ process.exit(1);
515
+ }
516
+ const imageInputs = await Promise.all(images.map((src) => resolveImageInput(src, logger)));
517
+ if (!opts.prompt && imageInputs.length === 0) {
518
+ logger.error("Provide --prompt and/or --input-image to describe the 3D content.");
519
+ process.exit(1);
520
+ }
521
+ const body = buildThreeDTaskBody({
522
+ model: opts.model,
523
+ prompt: opts.prompt,
524
+ flags: opts.flags,
525
+ seed: opts.seed ? Number.parseInt(opts.seed, 10) : void 0,
526
+ imageInputs
527
+ });
528
+ logger.info(
529
+ `Submitting 3D task (${opts.model}${imageInputs.length > 0 ? `, ${imageInputs.length} image ref${imageInputs.length === 1 ? "" : "s"}` : ""})`
530
+ );
531
+ const submitted = await submitThreeDTask(client, body, logger);
532
+ const taskId = submitted.id;
533
+ logger.debug(`taskId: ${taskId}`);
534
+ if (opts.async) {
535
+ console.log(taskId);
536
+ console.error(`Poll with: multix byteplus status ${taskId} --wait --download`);
537
+ return;
538
+ }
539
+ try {
540
+ const final = await waitForThreeDTask(client, taskId, {
541
+ waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 9e5,
542
+ logger
543
+ });
544
+ const url = final.content?.file_url;
545
+ if (!url) {
546
+ logger.error(`Task succeeded but no file_url in response: ${JSON.stringify(final)}`);
547
+ process.exit(1);
548
+ }
549
+ const ext = inferModelFileExt(url);
550
+ const outPath = opts.output ?? path5.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
551
+ await downloadModelFile(url, outPath, logger);
552
+ console.log(`
553
+ Task: ${taskId}`);
554
+ console.log(`Saved: ${outPath}`);
555
+ } catch (e) {
556
+ if (e instanceof PollTimeoutError) {
557
+ logger.error(
558
+ `Polling timed out \u2014 keep the task and check later: multix byteplus status ${taskId} --wait --download`
559
+ );
560
+ process.exit(1);
561
+ }
562
+ if (e instanceof PollFailedError) {
563
+ logger.error(`Task failed: ${JSON.stringify(e.value)}`);
564
+ process.exit(1);
565
+ }
566
+ throw e;
567
+ }
568
+ }
569
+ );
570
+ }
571
+
572
+ // src/providers/byteplus/generators/image.ts
573
+ init_http_client();
574
+ import fs5 from "fs";
575
+ import path6 from "path";
576
+ var ASPECT_TO_SIZE = {
577
+ "1:1": "2048x2048",
578
+ "16:9": "2560x1440",
579
+ "9:16": "1440x2560",
580
+ "4:3": "2048x1536",
581
+ "3:4": "1536x2048",
582
+ "21:9": "2560x1080",
583
+ "3:2": "2048x1365",
584
+ "2:3": "1365x2048"
585
+ };
586
+ function resolveSize(opts) {
587
+ if (opts.size && opts.aspectRatio) {
588
+ opts.logger?.warn("Both --size and --aspect-ratio given; using --size.");
589
+ return opts.size;
590
+ }
591
+ if (opts.size) return opts.size;
592
+ if (opts.aspectRatio) {
593
+ const mapped = ASPECT_TO_SIZE[opts.aspectRatio];
594
+ if (!mapped) {
595
+ throw new Error(
596
+ `Unsupported --aspect-ratio '${opts.aspectRatio}'. Supported: ${Object.keys(ASPECT_TO_SIZE).join(", ")}`
597
+ );
427
598
  }
428
599
  return mapped;
429
600
  }
@@ -465,14 +636,14 @@ async function generateSeedream(client, opts) {
465
636
  }
466
637
  const ext = (() => {
467
638
  try {
468
- const e = path5.extname(new URL(url).pathname);
639
+ const e = path6.extname(new URL(url).pathname);
469
640
  return e || ".jpg";
470
641
  } catch {
471
642
  return ".jpg";
472
643
  }
473
644
  })();
474
- const filename = opts.output && items.length === 1 ? path5.resolve(opts.output) : path5.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
475
- fs5.mkdirSync(path5.dirname(filename), { recursive: true });
645
+ const filename = opts.output && items.length === 1 ? path6.resolve(opts.output) : path6.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
646
+ fs5.mkdirSync(path6.dirname(filename), { recursive: true });
476
647
  await downloadFile(url, filename);
477
648
  saved.push({ path: filename, url, seed: item.seed });
478
649
  logger?.success(`Saved ${filename}`);
@@ -552,12 +723,12 @@ function collect2(value, prev) {
552
723
  }
553
724
 
554
725
  // src/providers/byteplus/commands/image-to-video.ts
555
- import path7 from "path";
726
+ import path8 from "path";
556
727
 
557
728
  // src/core/video-thumb.ts
558
729
  init_http_client();
559
730
  import fs6 from "fs";
560
- import path6 from "path";
731
+ import path7 from "path";
561
732
  var THUMB_KEYS = /* @__PURE__ */ new Set([
562
733
  "cover_image_url",
563
734
  "coverImageUrl",
@@ -607,9 +778,9 @@ function detectThumbUrl(obj) {
607
778
  async function downloadThumbBeside(thumbUrl, videoPath, logger) {
608
779
  const match = thumbUrl.match(IMAGE_EXT_RE);
609
780
  const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
610
- const dir = path6.dirname(videoPath);
611
- const base = path6.basename(videoPath, path6.extname(videoPath));
612
- const dest = path6.join(dir, `${base}_thumb.${ext}`);
781
+ const dir = path7.dirname(videoPath);
782
+ const base = path7.basename(videoPath, path7.extname(videoPath));
783
+ const dest = path7.join(dir, `${base}_thumb.${ext}`);
613
784
  try {
614
785
  await downloadFile(thumbUrl, dest);
615
786
  logger?.success(`Saved thumb ${dest}`);
@@ -628,10 +799,10 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
628
799
  }
629
800
  const saved = await downloadThumbBeside(url, videoPath, opts.logger);
630
801
  if (saved && opts.copyTo) {
631
- const copyExt = path6.extname(saved);
632
- const copyBase = path6.basename(opts.copyTo, path6.extname(opts.copyTo));
633
- const copyDir = path6.dirname(path6.resolve(opts.copyTo));
634
- const copyDest = path6.join(copyDir, `${copyBase}_thumb${copyExt}`);
802
+ const copyExt = path7.extname(saved);
803
+ const copyBase = path7.basename(opts.copyTo, path7.extname(opts.copyTo));
804
+ const copyDir = path7.dirname(path7.resolve(opts.copyTo));
805
+ const copyDest = path7.join(copyDir, `${copyBase}_thumb${copyExt}`);
635
806
  fs6.mkdirSync(copyDir, { recursive: true });
636
807
  fs6.copyFileSync(saved, copyDest);
637
808
  opts.logger?.success(`Copied thumb to ${copyDest}`);
@@ -639,41 +810,6 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
639
810
  return saved;
640
811
  }
641
812
 
642
- // src/providers/leonardo/poll.ts
643
- var PollTimeoutError = class extends Error {
644
- constructor() {
645
- super("Polling timed out before the operation completed.");
646
- this.name = "PollTimeoutError";
647
- }
648
- };
649
- var PollFailedError = class extends Error {
650
- constructor(value) {
651
- super("Operation failed.");
652
- this.value = value;
653
- this.name = "PollFailedError";
654
- }
655
- value;
656
- };
657
- async function poll(opts) {
658
- const interval = opts.intervalMs ?? 4e3;
659
- const max = opts.maxAttempts ?? 120;
660
- for (let attempt = 1; attempt <= max; attempt++) {
661
- if (opts.signal?.aborted) throw new Error("Aborted");
662
- const value = await opts.fetch();
663
- opts.onTick?.(attempt, value);
664
- if (opts.failed?.(value)) throw new PollFailedError(value);
665
- if (opts.done(value)) return value;
666
- await new Promise((resolve, reject) => {
667
- const t = setTimeout(resolve, interval);
668
- opts.signal?.addEventListener("abort", () => {
669
- clearTimeout(t);
670
- reject(new Error("Aborted"));
671
- });
672
- });
673
- }
674
- throw new PollTimeoutError();
675
- }
676
-
677
813
  // src/providers/byteplus/generators/video.ts
678
814
  init_http_client();
679
815
  function buildFlagSuffix(opts) {
@@ -796,7 +932,7 @@ function registerBytePlusImageToVideoCommand(parent) {
796
932
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
797
933
  process.exit(1);
798
934
  }
799
- const outPath = opts.output ?? path7.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
935
+ const outPath = opts.output ?? path8.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
800
936
  await downloadVideo(url, outPath, logger);
801
937
  await maybeDownloadThumb(final, outPath, {
802
938
  skip: opts.thumb === false,
@@ -822,7 +958,7 @@ Task: ${taskId}`);
822
958
  }
823
959
 
824
960
  // src/providers/byteplus/commands/reference-to-video.ts
825
- import path8 from "path";
961
+ import path9 from "path";
826
962
 
827
963
  // src/providers/byteplus/reference-input.ts
828
964
  var MAX_TOTAL_REFS = 12;
@@ -854,10 +990,10 @@ function toRef(kind, resolved, role) {
854
990
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
855
991
  }
856
992
  async function resolveRefSpec(kind, spec, logger) {
857
- const { path: path35, role } = parseRefSpec(spec);
858
- if (kind === "image") return toRef(kind, await resolveImageInput(path35, logger), role);
859
- if (kind === "video") return toRef(kind, await resolveVideoInput(path35, logger), role);
860
- return toRef(kind, await resolveAudioInput(path35, 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);
861
997
  }
862
998
  async function resolveRefSpecs(specs, logger) {
863
999
  const out = [];
@@ -954,7 +1090,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
954
1090
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
955
1091
  process.exit(1);
956
1092
  }
957
- const outPath = opts.output ?? path8.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1093
+ const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
958
1094
  await downloadVideo(url, outPath, logger);
959
1095
  await maybeDownloadThumb(final, outPath, {
960
1096
  skip: opts.thumb === false,
@@ -976,146 +1112,919 @@ Task: ${taskId}`);
976
1112
  throw e;
977
1113
  }
978
1114
  }
979
- );
1115
+ );
1116
+ }
1117
+
1118
+ // src/providers/byteplus/commands/status.ts
1119
+ import path10 from "path";
1120
+ function registerBytePlusStatusCommand(parent) {
1121
+ parent.command("status <taskId>").description("Get a Seedance task's status; --wait + --download to fetch the result MP4").option("--wait", "Poll until terminal status").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download MP4 on success (implies --wait)").option("--output <path>", "Output MP4 path (with --download)").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
1122
+ async (taskId, opts) => {
1123
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1124
+ const client = createBytePlusClient();
1125
+ const shouldWait = !!(opts.wait || opts.download);
1126
+ try {
1127
+ const status = shouldWait ? await waitForVideoTask(client, taskId, {
1128
+ waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 6e5,
1129
+ logger
1130
+ }) : await getVideoTaskStatus(client, taskId, logger);
1131
+ console.log(JSON.stringify(status, null, 2));
1132
+ if (opts.download && status.status === "succeeded") {
1133
+ const content = status.content;
1134
+ const videoUrl = content?.video_url;
1135
+ const fileUrl = content?.file_url;
1136
+ if (videoUrl) {
1137
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1138
+ await downloadVideo(videoUrl, outPath, logger);
1139
+ await maybeDownloadThumb(status, outPath, {
1140
+ skip: opts.thumb === false,
1141
+ copyTo: opts.output,
1142
+ logger
1143
+ });
1144
+ console.log(`Saved: ${outPath}`);
1145
+ } else if (fileUrl) {
1146
+ const ext = inferModelFileExt(fileUrl);
1147
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
1148
+ await downloadModelFile(fileUrl, outPath, logger);
1149
+ console.log(`Saved: ${outPath}`);
1150
+ } else {
1151
+ logger.error("Task succeeded but no video_url or file_url present");
1152
+ process.exit(1);
1153
+ }
1154
+ }
1155
+ } catch (e) {
1156
+ if (e instanceof PollTimeoutError) {
1157
+ logger.error(`Timed out waiting for task ${taskId}`);
1158
+ process.exit(1);
1159
+ }
1160
+ if (e instanceof PollFailedError) {
1161
+ logger.error(`Task failed: ${JSON.stringify(e.value)}`);
1162
+ process.exit(1);
1163
+ }
1164
+ throw e;
1165
+ }
1166
+ }
1167
+ );
1168
+ }
1169
+
1170
+ // src/providers/byteplus/commands/video.ts
1171
+ import path11 from "path";
1172
+ function registerBytePlusVideoCommand(parent) {
1173
+ parent.command("video").description("Generate a video from a text prompt via BytePlus Seedance 2.0").requiredOption("--prompt <text>", "Text prompt").option("-m, --model <id>", "Model id", BYTEPLUS_DEFAULTS.videoModel).option("--resolution <r>", "480p|720p|1080p|2k", BYTEPLUS_DEFAULTS.videoResolution).option("--duration <n>", "Duration seconds (4-15)", String(BYTEPLUS_DEFAULTS.videoDuration)).option(
1174
+ "--aspect-ratio <r>",
1175
+ "Aspect ratio (16:9, 9:16, 1:1, ...)",
1176
+ BYTEPLUS_DEFAULTS.videoAspectRatio
1177
+ ).option("--audio", "Enable audio track").option("--no-audio", "Disable audio track").option("--seed <n>", "Fixed seed").option("--negative <text>", "Negative prompt").option("--camera-fixed", "Lock camera (no motion)").option("--async", "Submit task and exit; print taskId only").option("--wait-timeout <ms>", "Poll timeout in ms", "600000").option("--output <path>", "Output MP4 path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
1178
+ async (opts) => {
1179
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1180
+ const client = createBytePlusClient();
1181
+ const body = buildVideoTaskBody({
1182
+ model: opts.model,
1183
+ prompt: opts.prompt,
1184
+ resolution: opts.resolution,
1185
+ duration: Number.parseInt(opts.duration, 10),
1186
+ aspectRatio: opts.aspectRatio,
1187
+ audio: opts.audio,
1188
+ seed: opts.seed ? Number.parseInt(opts.seed, 10) : void 0,
1189
+ negativePrompt: opts.negative,
1190
+ cameraFixed: opts.cameraFixed
1191
+ });
1192
+ logger.info(
1193
+ `Submitting Seedance task (${opts.model}, ${opts.resolution}, ${opts.duration}s)`
1194
+ );
1195
+ const submitted = await submitVideoTask(client, body, logger);
1196
+ const taskId = submitted.id;
1197
+ logger.debug(`taskId: ${taskId}`);
1198
+ if (opts.async) {
1199
+ console.log(taskId);
1200
+ console.error(`Poll with: multix byteplus status ${taskId}`);
1201
+ return;
1202
+ }
1203
+ try {
1204
+ const final = await waitForVideoTask(client, taskId, {
1205
+ waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 6e5,
1206
+ logger
1207
+ });
1208
+ const url = final.content?.video_url;
1209
+ if (!url) {
1210
+ logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
1211
+ process.exit(1);
1212
+ }
1213
+ const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1214
+ await downloadVideo(url, outPath, logger);
1215
+ await maybeDownloadThumb(final, outPath, {
1216
+ skip: opts.thumb === false,
1217
+ copyTo: opts.output,
1218
+ logger
1219
+ });
1220
+ console.log(`
1221
+ Task: ${taskId}`);
1222
+ console.log(`Saved: ${outPath}`);
1223
+ } catch (e) {
1224
+ if (e instanceof PollTimeoutError) {
1225
+ logger.error(
1226
+ `Polling timed out \u2014 try --async to keep the task and check later: multix byteplus status ${taskId}`
1227
+ );
1228
+ process.exit(1);
1229
+ }
1230
+ if (e instanceof PollFailedError) {
1231
+ logger.error(`Task failed: ${JSON.stringify(e.value)}`);
1232
+ process.exit(1);
1233
+ }
1234
+ throw e;
1235
+ }
1236
+ }
1237
+ );
1238
+ }
1239
+
1240
+ // src/providers/byteplus/commands/index.ts
1241
+ function registerBytePlusCommands(program2) {
1242
+ const byteplus = program2.command("byteplus").description(
1243
+ "BytePlus ModelArk: Seedream image, Seedance video, Hyper3D / Hitem3d 3D generation"
1244
+ );
1245
+ registerBytePlusGenerateCommand(byteplus);
1246
+ registerBytePlusImageToImageCommand(byteplus);
1247
+ registerBytePlusVideoCommand(byteplus);
1248
+ registerBytePlusImageToVideoCommand(byteplus);
1249
+ registerBytePlusReferenceToVideoCommand(byteplus);
1250
+ registerBytePlusGenerate3DCommand(byteplus);
1251
+ registerBytePlusStatusCommand(byteplus);
1252
+ }
1253
+
1254
+ // src/providers/elevenlabs/client.ts
1255
+ import fs7 from "fs";
1256
+ import path12 from "path";
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();
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");
980
1812
  }
981
1813
 
982
- // src/providers/byteplus/commands/status.ts
983
- import path9 from "path";
984
- function registerBytePlusStatusCommand(parent) {
985
- parent.command("status <taskId>").description("Get a Seedance task's status; --wait + --download to fetch the result MP4").option("--wait", "Poll until terminal status").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download MP4 on success (implies --wait)").option("--output <path>", "Output MP4 path (with --download)").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
986
- async (taskId, opts) => {
987
- const logger = createLogger({ verbose: opts.verbose ?? false });
988
- const client = createBytePlusClient();
989
- const shouldWait = !!(opts.wait || opts.download);
990
- try {
991
- const status = shouldWait ? await waitForVideoTask(client, taskId, {
992
- waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 6e5,
993
- logger
994
- }) : await getVideoTaskStatus(client, taskId, logger);
995
- console.log(JSON.stringify(status, null, 2));
996
- if (opts.download && status.status === "succeeded") {
997
- const url = status.content?.video_url;
998
- if (!url) {
999
- logger.error("Task succeeded but no video_url present");
1000
- process.exit(1);
1001
- }
1002
- const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1003
- await downloadVideo(url, outPath, logger);
1004
- await maybeDownloadThumb(status, outPath, {
1005
- skip: opts.thumb === false,
1006
- copyTo: opts.output,
1007
- logger
1008
- });
1009
- console.log(`Saved: ${outPath}`);
1010
- }
1011
- } catch (e) {
1012
- if (e instanceof PollTimeoutError) {
1013
- logger.error(`Timed out waiting for task ${taskId}`);
1014
- process.exit(1);
1015
- }
1016
- if (e instanceof PollFailedError) {
1017
- logger.error(`Task failed: ${JSON.stringify(e.value)}`);
1018
- process.exit(1);
1019
- }
1020
- throw e;
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
1021
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");
1022
1890
  }
1023
- );
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
+ });
1024
1901
  }
1025
1902
 
1026
- // src/providers/byteplus/commands/video.ts
1027
- import path10 from "path";
1028
- function registerBytePlusVideoCommand(parent) {
1029
- parent.command("video").description("Generate a video from a text prompt via BytePlus Seedance 2.0").requiredOption("--prompt <text>", "Text prompt").option("-m, --model <id>", "Model id", BYTEPLUS_DEFAULTS.videoModel).option("--resolution <r>", "480p|720p|1080p|2k", BYTEPLUS_DEFAULTS.videoResolution).option("--duration <n>", "Duration seconds (4-15)", String(BYTEPLUS_DEFAULTS.videoDuration)).option(
1030
- "--aspect-ratio <r>",
1031
- "Aspect ratio (16:9, 9:16, 1:1, ...)",
1032
- BYTEPLUS_DEFAULTS.videoAspectRatio
1033
- ).option("--audio", "Enable audio track").option("--no-audio", "Disable audio track").option("--seed <n>", "Fixed seed").option("--negative <text>", "Negative prompt").option("--camera-fixed", "Lock camera (no motion)").option("--async", "Submit task and exit; print taskId only").option("--wait-timeout <ms>", "Poll timeout in ms", "600000").option("--output <path>", "Output MP4 path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
1034
- async (opts) => {
1035
- const logger = createLogger({ verbose: opts.verbose ?? false });
1036
- const client = createBytePlusClient();
1037
- const body = buildVideoTaskBody({
1038
- model: opts.model,
1039
- prompt: opts.prompt,
1040
- resolution: opts.resolution,
1041
- duration: Number.parseInt(opts.duration, 10),
1042
- aspectRatio: opts.aspectRatio,
1043
- audio: opts.audio,
1044
- seed: opts.seed ? Number.parseInt(opts.seed, 10) : void 0,
1045
- negativePrompt: opts.negative,
1046
- cameraFixed: opts.cameraFixed
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
1047
1967
  });
1048
- logger.info(
1049
- `Submitting Seedance task (${opts.model}, ${opts.resolution}, ${opts.duration}s)`
1050
- );
1051
- const submitted = await submitVideoTask(client, body, logger);
1052
- const taskId = submitted.id;
1053
- logger.debug(`taskId: ${taskId}`);
1054
- if (opts.async) {
1055
- console.log(taskId);
1056
- console.error(`Poll with: multix byteplus status ${taskId}`);
1057
- return;
1058
- }
1059
- try {
1060
- const final = await waitForVideoTask(client, taskId, {
1061
- waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 6e5,
1062
- logger
1063
- });
1064
- const url = final.content?.video_url;
1065
- if (!url) {
1066
- logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
1067
- process.exit(1);
1068
- }
1069
- const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1070
- await downloadVideo(url, outPath, logger);
1071
- await maybeDownloadThumb(final, outPath, {
1072
- skip: opts.thumb === false,
1073
- copyTo: opts.output,
1074
- logger
1075
- });
1076
- console.log(`
1077
- Task: ${taskId}`);
1078
- console.log(`Saved: ${outPath}`);
1079
- } catch (e) {
1080
- if (e instanceof PollTimeoutError) {
1081
- logger.error(
1082
- `Polling timed out \u2014 try --async to keep the task and check later: multix byteplus status ${taskId}`
1083
- );
1084
- process.exit(1);
1085
- }
1086
- if (e instanceof PollFailedError) {
1087
- logger.error(`Task failed: ${JSON.stringify(e.value)}`);
1088
- process.exit(1);
1089
- }
1090
- throw e;
1091
- }
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(",");
1092
1983
  }
1093
- );
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
+ });
1094
1996
  }
1095
1997
 
1096
- // src/providers/byteplus/commands/index.ts
1097
- function registerBytePlusCommands(program2) {
1098
- const byteplus = program2.command("byteplus").description("BytePlus ModelArk: Seedream image generation, Seedance video generation");
1099
- registerBytePlusGenerateCommand(byteplus);
1100
- registerBytePlusImageToImageCommand(byteplus);
1101
- registerBytePlusVideoCommand(byteplus);
1102
- registerBytePlusImageToVideoCommand(byteplus);
1103
- registerBytePlusReferenceToVideoCommand(byteplus);
1104
- registerBytePlusStatusCommand(byteplus);
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);
1105
2014
  }
1106
2015
 
1107
2016
  // src/providers/gemini/commands/analyze.ts
1108
2017
  init_errors();
1109
- import fs9 from "fs";
2018
+ import fs14 from "fs";
1110
2019
 
1111
2020
  // src/providers/gemini/client.ts
1112
- import fs7 from "fs";
1113
- import path12 from "path";
2021
+ import fs12 from "fs";
2022
+ import path18 from "path";
1114
2023
  init_errors();
1115
2024
  init_http_client();
1116
2025
 
1117
2026
  // src/providers/gemini/task-resolver.ts
1118
- import path11 from "path";
2027
+ import path17 from "path";
1119
2028
  var MIME_MAP = {
1120
2029
  // Audio
1121
2030
  ".mp3": "audio/mp3",
@@ -1156,7 +2065,7 @@ var MIME_MAP = {
1156
2065
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1157
2066
  };
1158
2067
  function getMimeType(filePath) {
1159
- const ext = path11.extname(filePath).toLowerCase();
2068
+ const ext = path17.extname(filePath).toLowerCase();
1160
2069
  return MIME_MAP[ext] ?? "application/octet-stream";
1161
2070
  }
1162
2071
  function requiresProcessingWait(mimeType) {
@@ -1175,15 +2084,15 @@ function requireApiKey() {
1175
2084
  );
1176
2085
  return key;
1177
2086
  }
1178
- function authHeaders(apiKey) {
2087
+ function authHeaders2(apiKey) {
1179
2088
  return { "x-goog-api-key": apiKey };
1180
2089
  }
1181
2090
  async function uploadFile(filePath, opts = {}) {
1182
2091
  const apiKey = requireApiKey();
1183
2092
  const { timeoutMs = 3e5, logger } = opts;
1184
2093
  const mimeType = getMimeType(filePath);
1185
- const fileBytes = fs7.readFileSync(filePath);
1186
- const displayName = path12.basename(filePath);
2094
+ const fileBytes = fs12.readFileSync(filePath);
2095
+ const displayName = path18.basename(filePath);
1187
2096
  const boundary = `multix_${Date.now()}`;
1188
2097
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
1189
2098
  const body = Buffer.concat([
@@ -1228,7 +2137,7 @@ Content-Type: ${mimeType}\r
1228
2137
  await sleep(2e3);
1229
2138
  fileRef = await httpJson({
1230
2139
  url: `${BASE}/v1beta/${fileRef.name}`,
1231
- headers: authHeaders(apiKey)
2140
+ headers: authHeaders2(apiKey)
1232
2141
  }).then((r) => r.file);
1233
2142
  logger?.debug(`Processing... state=${fileRef.state}`);
1234
2143
  }
@@ -1245,7 +2154,7 @@ async function generateContent(req) {
1245
2154
  return httpJson({
1246
2155
  url: `${GENERATE_BASE}/${req.model}:generateContent`,
1247
2156
  method: "POST",
1248
- headers: authHeaders(apiKey),
2157
+ headers: authHeaders2(apiKey),
1249
2158
  body: {
1250
2159
  contents: req.contents,
1251
2160
  ...req.generationConfig ? { generationConfig: req.generationConfig } : {}
@@ -1272,12 +2181,12 @@ function extractImages(resp) {
1272
2181
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
1273
2182
  }
1274
2183
  function inlinePart(filePath) {
1275
- const data = fs7.readFileSync(filePath).toString("base64");
2184
+ const data = fs12.readFileSync(filePath).toString("base64");
1276
2185
  const mimeType = getMimeType(filePath);
1277
2186
  return { inlineData: { mimeType, data } };
1278
2187
  }
1279
2188
  function shouldUseFileApi(filePath) {
1280
- const stat = fs7.statSync(filePath);
2189
+ const stat = fs12.statSync(filePath);
1281
2190
  return stat.size > 20 * 1024 * 1024;
1282
2191
  }
1283
2192
  function sleep(ms) {
@@ -1330,13 +2239,13 @@ function getDefaultModel(task) {
1330
2239
  }
1331
2240
 
1332
2241
  // src/providers/gemini/result-formatter.ts
1333
- import fs8 from "fs";
1334
- import path13 from "path";
2242
+ import fs13 from "fs";
2243
+ import path19 from "path";
1335
2244
  function formatResults(results, format, outputPath) {
1336
2245
  const content = renderFormat(results, format);
1337
2246
  if (outputPath) {
1338
- fs8.mkdirSync(path13.dirname(path13.resolve(outputPath)), { recursive: true });
1339
- fs8.writeFileSync(outputPath, content, "utf8");
2247
+ fs13.mkdirSync(path19.dirname(path19.resolve(outputPath)), { recursive: true });
2248
+ fs13.writeFileSync(outputPath, content, "utf8");
1340
2249
  } else {
1341
2250
  process.stdout.write(content);
1342
2251
  }
@@ -1403,7 +2312,7 @@ function registerAnalyzeCommand(parent) {
1403
2312
  const logger = createLogger({ verbose: opts.verbose ?? false });
1404
2313
  const model = opts.model ?? getDefaultModel("analyze");
1405
2314
  for (const f of opts.files) {
1406
- if (!fs9.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2315
+ if (!fs14.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
1407
2316
  }
1408
2317
  const results = [];
1409
2318
  for (let i = 0; i < opts.files.length; i++) {
@@ -1449,14 +2358,14 @@ function registerAnalyzeCommand(parent) {
1449
2358
 
1450
2359
  // src/providers/gemini/commands/extract.ts
1451
2360
  init_errors();
1452
- import fs10 from "fs";
2361
+ import fs15 from "fs";
1453
2362
  function registerExtractCommand(parent) {
1454
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(
1455
2364
  async (opts) => {
1456
2365
  const logger = createLogger({ verbose: opts.verbose ?? false });
1457
2366
  const model = opts.model ?? getDefaultModel("extract");
1458
2367
  for (const f of opts.files) {
1459
- if (!fs10.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2368
+ if (!fs15.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
1460
2369
  }
1461
2370
  const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
1462
2371
  const results = [];
@@ -1498,8 +2407,8 @@ function registerExtractCommand(parent) {
1498
2407
  init_errors();
1499
2408
 
1500
2409
  // src/providers/gemini/generators/speech.ts
1501
- import fs11 from "fs";
1502
- import path14 from "path";
2410
+ import fs16 from "fs";
2411
+ import path20 from "path";
1503
2412
 
1504
2413
  // src/providers/gemini/voices.ts
1505
2414
  var GEMINI_TTS_VOICES = [
@@ -1582,12 +2491,12 @@ async function generateGeminiSpeech(opts) {
1582
2491
  const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
1583
2492
  const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
1584
2493
  const outDir = getOutputDir();
1585
- const dest = path14.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
1586
- fs11.writeFileSync(dest, outBytes);
2494
+ const dest = path20.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
2495
+ fs16.writeFileSync(dest, outBytes);
1587
2496
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
1588
2497
  if (output) {
1589
- fs11.mkdirSync(path14.dirname(path14.resolve(output)), { recursive: true });
1590
- fs11.copyFileSync(dest, output);
2498
+ fs16.mkdirSync(path20.dirname(path20.resolve(output)), { recursive: true });
2499
+ fs16.copyFileSync(dest, output);
1591
2500
  logger?.success(`Copied to: ${output}`);
1592
2501
  }
1593
2502
  return { status: "success", generatedAudio: dest, model, mimeType: audio.mimeType };
@@ -1686,8 +2595,8 @@ function collectSpeakers(value, prev) {
1686
2595
  }
1687
2596
 
1688
2597
  // src/providers/gemini/commands/generate-video.ts
1689
- import fs12 from "fs";
1690
- import path15 from "path";
2598
+ import fs17 from "fs";
2599
+ import path21 from "path";
1691
2600
  init_errors();
1692
2601
  init_http_client();
1693
2602
  var BASE2 = "https://generativelanguage.googleapis.com";
@@ -1721,10 +2630,10 @@ function registerGenerateVideoCommand(parent) {
1721
2630
  const contentParts = [{ text: opts.prompt }];
1722
2631
  if (opts.referenceImages?.length) {
1723
2632
  for (const imgPath of opts.referenceImages.slice(0, 2)) {
1724
- if (!fs12.existsSync(imgPath))
2633
+ if (!fs17.existsSync(imgPath))
1725
2634
  throw new ValidationError(`Reference image not found: ${imgPath}`);
1726
- const data = fs12.readFileSync(imgPath).toString("base64");
1727
- const ext = path15.extname(imgPath).toLowerCase();
2635
+ const data = fs17.readFileSync(imgPath).toString("base64");
2636
+ const ext = path21.extname(imgPath).toLowerCase();
1728
2637
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
1729
2638
  contentParts.push({ inlineData: { mimeType, data } });
1730
2639
  }
@@ -1773,13 +2682,13 @@ function registerGenerateVideoCommand(parent) {
1773
2682
  );
1774
2683
  }
1775
2684
  const outDir = getOutputDir();
1776
- const dest = path15.join(outDir, `veo_generated_${Date.now()}.mp4`);
2685
+ const dest = path21.join(outDir, `veo_generated_${Date.now()}.mp4`);
1777
2686
  const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
1778
2687
  await downloadFile2(videoUri, dest);
1779
2688
  logger.success(`Saved video: ${dest}`);
1780
2689
  if (opts.output) {
1781
- fs12.mkdirSync(path15.dirname(path15.resolve(opts.output)), { recursive: true });
1782
- fs12.copyFileSync(dest, opts.output);
2690
+ fs17.mkdirSync(path21.dirname(path21.resolve(opts.output)), { recursive: true });
2691
+ fs17.copyFileSync(dest, opts.output);
1783
2692
  logger.success(`Copied to: ${opts.output}`);
1784
2693
  }
1785
2694
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -1798,8 +2707,8 @@ function sleep2(ms) {
1798
2707
 
1799
2708
  // src/providers/gemini/commands/generate.ts
1800
2709
  init_errors();
1801
- import fs13 from "fs";
1802
- import path16 from "path";
2710
+ import fs18 from "fs";
2711
+ import path22 from "path";
1803
2712
  function registerGenerateCommand(parent) {
1804
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(
1805
2714
  async (opts) => {
@@ -1841,8 +2750,8 @@ function registerGenerateCommand(parent) {
1841
2750
  const outDir = getOutputDir();
1842
2751
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
1843
2752
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
1844
- const dest = path16.join(outDir, fname);
1845
- fs13.writeFileSync(dest, Buffer.from(img.data, "base64"));
2753
+ const dest = path22.join(outDir, fname);
2754
+ fs18.writeFileSync(dest, Buffer.from(img.data, "base64"));
1846
2755
  savedFiles.push(dest);
1847
2756
  logger.success(`Saved: ${dest}`);
1848
2757
  }
@@ -1857,8 +2766,8 @@ function registerGenerateCommand(parent) {
1857
2766
  process.exit(1);
1858
2767
  }
1859
2768
  if (opts.output && savedFiles[0]) {
1860
- fs13.mkdirSync(path16.dirname(path16.resolve(opts.output)), { recursive: true });
1861
- fs13.copyFileSync(savedFiles[0], opts.output);
2769
+ fs18.mkdirSync(path22.dirname(path22.resolve(opts.output)), { recursive: true });
2770
+ fs18.copyFileSync(savedFiles[0], opts.output);
1862
2771
  logger.success(`Copied to: ${opts.output}`);
1863
2772
  }
1864
2773
  console.log(`
@@ -1869,8 +2778,8 @@ Generated ${savedFiles.length} image(s):`);
1869
2778
  }
1870
2779
 
1871
2780
  // src/providers/gemini/commands/image-to-image.ts
1872
- import fs14 from "fs";
1873
- import path17 from "path";
2781
+ import fs19 from "fs";
2782
+ import path23 from "path";
1874
2783
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
1875
2784
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
1876
2785
  function registerGeminiImageToImageCommand(parent) {
@@ -1931,14 +2840,14 @@ function registerGeminiImageToImageCommand(parent) {
1931
2840
  const img = images[i];
1932
2841
  if (!img) continue;
1933
2842
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
1934
- const dest = path17.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
1935
- 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"));
1936
2845
  saved.push(dest);
1937
2846
  logger.success(`Saved: ${dest}`);
1938
2847
  }
1939
2848
  if (opts.output && saved[0]) {
1940
- fs14.mkdirSync(path17.dirname(path17.resolve(opts.output)), { recursive: true });
1941
- fs14.copyFileSync(saved[0], opts.output);
2849
+ fs19.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
2850
+ fs19.copyFileSync(saved[0], opts.output);
1942
2851
  logger.success(`Copied to: ${opts.output}`);
1943
2852
  }
1944
2853
  console.log(`
@@ -1965,7 +2874,7 @@ function sniffImageMime(buf, url) {
1965
2874
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
1966
2875
  return "image/webp";
1967
2876
  }
1968
- const ext = path17.extname(new URL(url).pathname).toLowerCase();
2877
+ const ext = path23.extname(new URL(url).pathname).toLowerCase();
1969
2878
  if (ext === ".png") return "image/png";
1970
2879
  if (ext === ".webp") return "image/webp";
1971
2880
  if (ext === ".gif") return "image/gif";
@@ -1973,17 +2882,17 @@ function sniffImageMime(buf, url) {
1973
2882
  }
1974
2883
 
1975
2884
  // src/providers/gemini/commands/image-to-video.ts
1976
- import fs15 from "fs";
1977
- import path18 from "path";
2885
+ import fs20 from "fs";
2886
+ import path24 from "path";
1978
2887
  init_errors();
1979
2888
  init_http_client();
1980
2889
  var BASE3 = "https://generativelanguage.googleapis.com";
1981
2890
  function imagePart(imgPath) {
1982
- if (!fs15.existsSync(imgPath)) {
2891
+ if (!fs20.existsSync(imgPath)) {
1983
2892
  throw new ValidationError(`Image not found: ${imgPath}`);
1984
2893
  }
1985
- const data = fs15.readFileSync(imgPath).toString("base64");
1986
- const ext = path18.extname(imgPath).toLowerCase();
2894
+ const data = fs20.readFileSync(imgPath).toString("base64");
2895
+ const ext = path24.extname(imgPath).toLowerCase();
1987
2896
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
1988
2897
  return { inlineData: { mimeType, data } };
1989
2898
  }
@@ -2056,12 +2965,12 @@ function registerGeminiImageToVideoCommand(parent) {
2056
2965
  "gemini"
2057
2966
  );
2058
2967
  }
2059
- const dest = path18.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
2968
+ const dest = path24.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
2060
2969
  await downloadFile(videoUri, dest);
2061
2970
  logger.success(`Saved video: ${dest}`);
2062
2971
  if (opts.output) {
2063
- fs15.mkdirSync(path18.dirname(path18.resolve(opts.output)), { recursive: true });
2064
- fs15.copyFileSync(dest, opts.output);
2972
+ fs20.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
2973
+ fs20.copyFileSync(dest, opts.output);
2065
2974
  logger.success(`Copied to: ${opts.output}`);
2066
2975
  }
2067
2976
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -2077,14 +2986,14 @@ Generated video: ${dest}`);
2077
2986
 
2078
2987
  // src/providers/gemini/commands/transcribe.ts
2079
2988
  init_errors();
2080
- import fs16 from "fs";
2081
- function registerTranscribeCommand(parent) {
2989
+ import fs21 from "fs";
2990
+ function registerTranscribeCommand2(parent) {
2082
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(
2083
2992
  async (opts) => {
2084
2993
  const logger = createLogger({ verbose: opts.verbose ?? false });
2085
2994
  const model = opts.model ?? getDefaultModel("transcribe");
2086
2995
  for (const f of opts.files) {
2087
- if (!fs16.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2996
+ if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2088
2997
  }
2089
2998
  const results = [];
2090
2999
  for (let i = 0; i < opts.files.length; i++) {
@@ -2131,7 +3040,7 @@ function registerGeminiCommands(program2) {
2131
3040
  "Gemini AI operations: analyze, transcribe, extract, generate images/video/speech"
2132
3041
  );
2133
3042
  registerAnalyzeCommand(gemini);
2134
- registerTranscribeCommand(gemini);
3043
+ registerTranscribeCommand2(gemini);
2135
3044
  registerExtractCommand(gemini);
2136
3045
  registerGenerateCommand(gemini);
2137
3046
  registerGeminiImageToImageCommand(gemini);
@@ -2142,8 +3051,8 @@ function registerGeminiCommands(program2) {
2142
3051
 
2143
3052
  // src/providers/leonardo/commands/generate.ts
2144
3053
  init_http_client();
2145
- import fs17 from "fs";
2146
- import path19 from "path";
3054
+ import fs22 from "fs";
3055
+ import path25 from "path";
2147
3056
 
2148
3057
  // src/providers/leonardo/client.ts
2149
3058
  init_errors();
@@ -2217,11 +3126,11 @@ var LeonardoClient = class {
2217
3126
  }
2218
3127
  apiKey;
2219
3128
  baseUrl;
2220
- async request(path35, opts = {}) {
3129
+ async request(path41, opts = {}) {
2221
3130
  const base = this.baseUrl.replace(/\/$/, "");
2222
- const v2Override = path35.startsWith("v2:");
3131
+ const v2Override = path41.startsWith("v2:");
2223
3132
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
2224
- const finalPath = v2Override ? path35.slice(3) : path35;
3133
+ const finalPath = v2Override ? path41.slice(3) : path41;
2225
3134
  const url = new URL(finalBase + finalPath);
2226
3135
  if (opts.query) {
2227
3136
  for (const [k, v] of Object.entries(opts.query)) {
@@ -2240,14 +3149,14 @@ var LeonardoClient = class {
2240
3149
  timeoutMs: opts.timeoutMs ?? 12e4
2241
3150
  });
2242
3151
  }
2243
- get(path35, query, logger) {
2244
- return this.request(path35, { method: "GET", query, logger });
3152
+ get(path41, query, logger) {
3153
+ return this.request(path41, { method: "GET", query, logger });
2245
3154
  }
2246
- post(path35, body, logger) {
2247
- return this.request(path35, { method: "POST", body, logger });
3155
+ post(path41, body, logger) {
3156
+ return this.request(path41, { method: "POST", body, logger });
2248
3157
  }
2249
- delete(path35, logger) {
2250
- return this.request(path35, { method: "DELETE", logger });
3158
+ delete(path41, logger) {
3159
+ return this.request(path41, { method: "DELETE", logger });
2251
3160
  }
2252
3161
  };
2253
3162
  function createLeonardoClient() {
@@ -2258,7 +3167,7 @@ function createLeonardoClient() {
2258
3167
  function extFromUrl(url, fallback = ".png") {
2259
3168
  try {
2260
3169
  const u = new URL(url);
2261
- const e = path19.extname(u.pathname);
3170
+ const e = path25.extname(u.pathname);
2262
3171
  return e.length > 0 ? e : fallback;
2263
3172
  } catch {
2264
3173
  return fallback;
@@ -2343,7 +3252,7 @@ function registerLeonardoGenerateCommand(parent) {
2343
3252
  if (!img) continue;
2344
3253
  const ext = extFromUrl(img.url);
2345
3254
  const filename = autoFilename(generationId, i + 1, ext);
2346
- const filepath = path19.join(outDir, filename);
3255
+ const filepath = path25.join(outDir, filename);
2347
3256
  try {
2348
3257
  await downloadFile(img.url, filepath);
2349
3258
  saved.push(filepath);
@@ -2353,9 +3262,9 @@ function registerLeonardoGenerateCommand(parent) {
2353
3262
  }
2354
3263
  }
2355
3264
  if (opts.output && saved[0]) {
2356
- const dest = path19.resolve(opts.output);
2357
- fs17.mkdirSync(path19.dirname(dest), { recursive: true });
2358
- 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);
2359
3268
  logger.success(`Copied to ${dest}`);
2360
3269
  }
2361
3270
  console.log(`
@@ -2367,11 +3276,11 @@ Generated ${saved.length} image(s):`);
2367
3276
 
2368
3277
  // src/providers/leonardo/commands/image-to-image.ts
2369
3278
  init_http_client();
2370
- import fs18 from "fs";
2371
- import path20 from "path";
3279
+ import fs23 from "fs";
3280
+ import path26 from "path";
2372
3281
  function extFromUrl2(url, fallback = ".png") {
2373
3282
  try {
2374
- return path20.extname(new URL(url).pathname) || fallback;
3283
+ return path26.extname(new URL(url).pathname) || fallback;
2375
3284
  } catch {
2376
3285
  return fallback;
2377
3286
  }
@@ -2433,7 +3342,7 @@ function registerLeonardoImageToImageCommand(parent) {
2433
3342
  if (!img) continue;
2434
3343
  const ext = extFromUrl2(img.url);
2435
3344
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
2436
- const filepath = path20.join(outDir, filename);
3345
+ const filepath = path26.join(outDir, filename);
2437
3346
  try {
2438
3347
  await downloadFile(img.url, filepath);
2439
3348
  saved.push(filepath);
@@ -2443,9 +3352,9 @@ function registerLeonardoImageToImageCommand(parent) {
2443
3352
  }
2444
3353
  }
2445
3354
  if (opts.output && saved[0]) {
2446
- const dest = path20.resolve(opts.output);
2447
- fs18.mkdirSync(path20.dirname(dest), { recursive: true });
2448
- 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);
2449
3358
  logger.success(`Copied to ${dest}`);
2450
3359
  }
2451
3360
  console.log(`
@@ -2457,7 +3366,7 @@ Generated ${saved.length} image(s):`);
2457
3366
 
2458
3367
  // src/providers/leonardo/commands/video.ts
2459
3368
  init_http_client();
2460
- import path21 from "path";
3369
+ import path27 from "path";
2461
3370
  function v2DimsFor(resolution) {
2462
3371
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
2463
3372
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -2568,7 +3477,7 @@ async function pollAndDownload(client, jobId, opts) {
2568
3477
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
2569
3478
  process.exit(1);
2570
3479
  }
2571
- const outPath = opts.output ?? path21.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
3480
+ const outPath = opts.output ?? path27.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
2572
3481
  await downloadFile(videoUrl, outPath);
2573
3482
  logger.success(`Saved ${outPath}`);
2574
3483
  await maybeDownloadThumb(gen, outPath, {
@@ -2675,7 +3584,7 @@ function registerLeonardoModelsCommand(parent) {
2675
3584
 
2676
3585
  // src/providers/leonardo/commands/status.ts
2677
3586
  init_http_client();
2678
- import path22 from "path";
3587
+ import path28 from "path";
2679
3588
  var VIDEO_KEYS = ["motionMP4URL", "url"];
2680
3589
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
2681
3590
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -2740,7 +3649,7 @@ function registerLeonardoStatusCommand(parent) {
2740
3649
  const outDir = getOutputDir();
2741
3650
  let idx = 0;
2742
3651
  for (const url of videos) {
2743
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path22.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`);
2744
3653
  await downloadFile(url, dest);
2745
3654
  logger.success(`Saved ${dest}`);
2746
3655
  await maybeDownloadThumb(gen, dest, {
@@ -2753,7 +3662,7 @@ function registerLeonardoStatusCommand(parent) {
2753
3662
  for (const url of images) {
2754
3663
  const m = url.match(IMAGE_EXT_RE2);
2755
3664
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
2756
- const dest = path22.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
3665
+ const dest = path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
2757
3666
  await downloadFile(url, dest);
2758
3667
  logger.success(`Saved ${dest}`);
2759
3668
  idx++;
@@ -2841,7 +3750,7 @@ function requireMinimaxKey() {
2841
3750
  }
2842
3751
  return key;
2843
3752
  }
2844
- function authHeaders2(apiKey) {
3753
+ function authHeaders3(apiKey) {
2845
3754
  return {
2846
3755
  Authorization: `Bearer ${apiKey}`,
2847
3756
  "Content-Type": "application/json"
@@ -2853,7 +3762,7 @@ async function apiPost(endpoint, payload, apiKey, opts = {}) {
2853
3762
  const data = await httpJson({
2854
3763
  url,
2855
3764
  method: "POST",
2856
- headers: authHeaders2(apiKey),
3765
+ headers: authHeaders3(apiKey),
2857
3766
  body: payload,
2858
3767
  timeoutMs: opts.timeoutMs ?? 12e4
2859
3768
  });
@@ -2866,17 +3775,17 @@ async function apiPost(endpoint, payload, apiKey, opts = {}) {
2866
3775
  }
2867
3776
  return data;
2868
3777
  }
2869
- async function apiGet(endpoint, params, apiKey) {
3778
+ async function apiGet2(endpoint, params, apiKey) {
2870
3779
  const url = new URL(`${MINIMAX_BASE_URL}/${endpoint}`);
2871
3780
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
2872
- return httpJson({ url: url.toString(), headers: authHeaders2(apiKey) });
3781
+ return httpJson({ url: url.toString(), headers: authHeaders3(apiKey) });
2873
3782
  }
2874
3783
  async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
2875
3784
  const { intervalMs = 1e4, timeoutMs = 6e5, logger } = opts;
2876
3785
  const deadline = Date.now() + timeoutMs;
2877
3786
  let elapsed = 0;
2878
3787
  while (Date.now() < deadline) {
2879
- const result = await apiGet(`query/${taskType}`, { task_id: taskId }, apiKey);
3788
+ const result = await apiGet2(`query/${taskType}`, { task_id: taskId }, apiKey);
2880
3789
  const status = result.status ?? "Unknown";
2881
3790
  if (elapsed > 0 && elapsed % 3e4 < intervalMs) {
2882
3791
  logger?.debug(`Polling... ${Math.round(elapsed / 1e3)}s elapsed, status: ${status}`);
@@ -2891,7 +3800,7 @@ async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
2891
3800
  throw new ProviderError(`Task ${taskId} timed out after ${timeoutMs / 1e3}s`, "minimax");
2892
3801
  }
2893
3802
  async function downloadMinimaxFile(fileId, apiKey, dest, logger) {
2894
- const result = await apiGet(
3803
+ const result = await apiGet2(
2895
3804
  "files/retrieve",
2896
3805
  { file_id: fileId },
2897
3806
  apiKey
@@ -2910,8 +3819,8 @@ function sleep4(ms) {
2910
3819
 
2911
3820
  // src/providers/minimax/generators/music.ts
2912
3821
  init_http_client();
2913
- import fs19 from "fs";
2914
- import path23 from "path";
3822
+ import fs24 from "fs";
3823
+ import path29 from "path";
2915
3824
  async function generateMinimaxMusic(opts) {
2916
3825
  const {
2917
3826
  apiKey,
@@ -2956,14 +3865,14 @@ async function generateMinimaxMusic(opts) {
2956
3865
  }
2957
3866
  const durationMs = resp.extra_info?.music_duration ?? 0;
2958
3867
  const outDir = getOutputDir();
2959
- const dest = path23.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3868
+ const dest = path29.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
2960
3869
  try {
2961
3870
  if (audioData.startsWith("http")) {
2962
3871
  const bytes = await fetchBytes(audioData);
2963
- fs19.writeFileSync(dest, bytes);
3872
+ fs24.writeFileSync(dest, bytes);
2964
3873
  } else {
2965
3874
  const audioBytes = Buffer.from(audioData, "hex");
2966
- fs19.writeFileSync(dest, audioBytes);
3875
+ fs24.writeFileSync(dest, audioBytes);
2967
3876
  }
2968
3877
  } catch (e) {
2969
3878
  return {
@@ -2974,8 +3883,8 @@ async function generateMinimaxMusic(opts) {
2974
3883
  const durSec = durationMs / 1e3;
2975
3884
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
2976
3885
  if (output) {
2977
- fs19.mkdirSync(path23.dirname(path23.resolve(output)), { recursive: true });
2978
- fs19.copyFileSync(dest, output);
3886
+ fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3887
+ fs24.copyFileSync(dest, output);
2979
3888
  }
2980
3889
  return { status: "success", generatedAudio: dest, durationMs, model };
2981
3890
  }
@@ -3003,7 +3912,7 @@ var ALL_MINIMAX_MODELS = /* @__PURE__ */ new Set([
3003
3912
  ...MINIMAX_SPEECH_MODELS,
3004
3913
  ...MINIMAX_MUSIC_MODELS
3005
3914
  ]);
3006
- var TASK_DEFAULTS = {
3915
+ var TASK_DEFAULTS2 = {
3007
3916
  image: process.env.MINIMAX_IMAGE_MODEL ?? "image-01",
3008
3917
  video: process.env.MINIMAX_VIDEO_MODEL ?? "MiniMax-Hailuo-2.3",
3009
3918
  speech: process.env.MINIMAX_SPEECH_MODEL ?? "speech-2.8-hd",
@@ -3022,7 +3931,7 @@ function registerMinimaxGenerateMusicCommand(parent) {
3022
3931
  if (!opts.lyrics && !opts.prompt) {
3023
3932
  throw new ValidationError("Either --lyrics or --prompt is required");
3024
3933
  }
3025
- const model = opts.model ?? TASK_DEFAULTS.music;
3934
+ const model = opts.model ?? TASK_DEFAULTS2.music;
3026
3935
  const result = await generateMinimaxMusic({
3027
3936
  apiKey,
3028
3937
  lyrics: opts.lyrics,
@@ -3049,8 +3958,8 @@ Generated music: ${result.generatedAudio}`);
3049
3958
  init_errors();
3050
3959
 
3051
3960
  // src/providers/minimax/generators/speech.ts
3052
- import fs20 from "fs";
3053
- import path24 from "path";
3961
+ import fs25 from "fs";
3962
+ import path30 from "path";
3054
3963
  async function generateMinimaxSpeech(opts) {
3055
3964
  const {
3056
3965
  apiKey,
@@ -3097,12 +4006,12 @@ async function generateMinimaxSpeech(opts) {
3097
4006
  const audioBytes = Buffer.from(audioHex, "hex");
3098
4007
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
3099
4008
  const outDir = getOutputDir();
3100
- const dest = path24.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
3101
- fs20.writeFileSync(dest, audioBytes);
4009
+ const dest = path30.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4010
+ fs25.writeFileSync(dest, audioBytes);
3102
4011
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
3103
4012
  if (output) {
3104
- fs20.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3105
- fs20.copyFileSync(dest, output);
4013
+ fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4014
+ fs25.copyFileSync(dest, output);
3106
4015
  }
3107
4016
  return { status: "success", generatedAudio: dest, model };
3108
4017
  }
@@ -3122,7 +4031,7 @@ function registerMinimaxGenerateSpeechCommand(parent) {
3122
4031
  if (!SPEECH_FORMATS.includes(opts.outputFormat)) {
3123
4032
  throw new ValidationError(`--output-format must be one of: ${SPEECH_FORMATS.join(", ")}`);
3124
4033
  }
3125
- const model = opts.model ?? TASK_DEFAULTS.speech;
4034
+ const model = opts.model ?? TASK_DEFAULTS2.speech;
3126
4035
  const rate = Number.parseFloat(opts.rate);
3127
4036
  const result = await generateMinimaxSpeech({
3128
4037
  apiKey,
@@ -3149,8 +4058,8 @@ Generated audio: ${result.generatedAudio}`);
3149
4058
  init_errors();
3150
4059
 
3151
4060
  // src/providers/minimax/generators/video.ts
3152
- import fs21 from "fs";
3153
- import path25 from "path";
4061
+ import fs26 from "fs";
4062
+ import path31 from "path";
3154
4063
  async function generateMinimaxVideo(opts) {
3155
4064
  const {
3156
4065
  apiKey,
@@ -3196,18 +4105,18 @@ async function generateMinimaxVideo(opts) {
3196
4105
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
3197
4106
  }
3198
4107
  const outDir = getOutputDir();
3199
- const dest = path25.join(outDir, `minimax_video_${Date.now()}.mp4`);
4108
+ const dest = path31.join(outDir, `minimax_video_${Date.now()}.mp4`);
3200
4109
  try {
3201
4110
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
3202
4111
  } catch (e) {
3203
4112
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
3204
4113
  }
3205
4114
  const elapsed = (Date.now() - start) / 1e3;
3206
- const stat = fs21.statSync(dest);
4115
+ const stat = fs26.statSync(dest);
3207
4116
  const fileSizeMb = stat.size / (1024 * 1024);
3208
4117
  if (output) {
3209
- fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
3210
- fs21.copyFileSync(dest, output);
4118
+ fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4119
+ fs26.copyFileSync(dest, output);
3211
4120
  }
3212
4121
  await maybeDownloadThumb(pollResult, dest, {
3213
4122
  skip: thumb === false,
@@ -3230,7 +4139,7 @@ function registerMinimaxGenerateVideoCommand(parent) {
3230
4139
  async (opts) => {
3231
4140
  const logger = createLogger({ verbose: opts.verbose ?? false });
3232
4141
  const apiKey = requireMinimaxKey();
3233
- const model = opts.model ?? TASK_DEFAULTS.video;
4142
+ const model = opts.model ?? TASK_DEFAULTS2.video;
3234
4143
  const duration = Number.parseInt(opts.duration, 10);
3235
4144
  if (!VIDEO_DURATIONS.includes(duration)) {
3236
4145
  throw new ValidationError(`--duration must be one of: ${VIDEO_DURATIONS.join(", ")}`);
@@ -3270,8 +4179,8 @@ Generated video: ${result.generatedVideo}`);
3270
4179
  // src/providers/minimax/generators/image.ts
3271
4180
  init_errors();
3272
4181
  init_http_client();
3273
- import fs22 from "fs";
3274
- import path26 from "path";
4182
+ import fs27 from "fs";
4183
+ import path32 from "path";
3275
4184
  async function generateMinimaxImage(opts) {
3276
4185
  const {
3277
4186
  apiKey,
@@ -3309,8 +4218,8 @@ async function generateMinimaxImage(opts) {
3309
4218
  try {
3310
4219
  const bytes = await fetchBytes(url);
3311
4220
  const fname = `minimax_image_${Date.now()}_${i}.png`;
3312
- const dest = path26.join(outDir, fname);
3313
- fs22.writeFileSync(dest, bytes);
4221
+ const dest = path32.join(outDir, fname);
4222
+ fs27.writeFileSync(dest, bytes);
3314
4223
  savedFiles.push(dest);
3315
4224
  logger?.success(`Saved: ${dest}`);
3316
4225
  } catch (e) {
@@ -3321,8 +4230,8 @@ async function generateMinimaxImage(opts) {
3321
4230
  }
3322
4231
  }
3323
4232
  if (output && savedFiles[0]) {
3324
- fs22.mkdirSync(path26.dirname(path26.resolve(output)), { recursive: true });
3325
- fs22.copyFileSync(savedFiles[0], output);
4233
+ fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4234
+ fs27.copyFileSync(savedFiles[0], output);
3326
4235
  }
3327
4236
  return { status: "success", generatedImages: savedFiles, model };
3328
4237
  }
@@ -3333,7 +4242,7 @@ function registerMinimaxGenerateCommand(parent) {
3333
4242
  async (opts) => {
3334
4243
  const logger = createLogger({ verbose: opts.verbose ?? false });
3335
4244
  const apiKey = requireMinimaxKey();
3336
- const model = opts.model ?? TASK_DEFAULTS.image;
4245
+ const model = opts.model ?? TASK_DEFAULTS2.image;
3337
4246
  const numImages = Math.min(Math.max(Number.parseInt(opts.numImages, 10) || 1, 1), 9);
3338
4247
  const result = await generateMinimaxImage({
3339
4248
  apiKey,
@@ -3358,8 +4267,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
3358
4267
  // src/providers/minimax/commands/image-to-image.ts
3359
4268
  init_errors();
3360
4269
  init_http_client();
3361
- import fs23 from "fs";
3362
- import path27 from "path";
4270
+ import fs28 from "fs";
4271
+ import path33 from "path";
3363
4272
  function registerMinimaxImageToImageCommand(parent) {
3364
4273
  parent.command("image-to-image").alias("i2i").description(
3365
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."
@@ -3370,7 +4279,7 @@ function registerMinimaxImageToImageCommand(parent) {
3370
4279
  async (opts) => {
3371
4280
  const logger = createLogger({ verbose: opts.verbose ?? false });
3372
4281
  const apiKey = requireMinimaxKey();
3373
- const model = opts.model ?? TASK_DEFAULTS.image;
4282
+ const model = opts.model ?? TASK_DEFAULTS2.image;
3374
4283
  const numImages = Math.min(Math.max(Number.parseInt(opts.numImages, 10) || 1, 1), 9);
3375
4284
  logger.warn(
3376
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."
@@ -3414,8 +4323,8 @@ function registerMinimaxImageToImageCommand(parent) {
3414
4323
  if (!url) continue;
3415
4324
  try {
3416
4325
  const bytes = await fetchBytes(url);
3417
- const dest = path27.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
3418
- fs23.writeFileSync(dest, bytes);
4326
+ const dest = path33.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4327
+ fs28.writeFileSync(dest, bytes);
3419
4328
  saved.push(dest);
3420
4329
  logger.success(`Saved: ${dest}`);
3421
4330
  } catch (e) {
@@ -3426,8 +4335,8 @@ function registerMinimaxImageToImageCommand(parent) {
3426
4335
  }
3427
4336
  }
3428
4337
  if (opts.output && saved[0]) {
3429
- fs23.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
3430
- fs23.copyFileSync(saved[0], opts.output);
4338
+ fs28.mkdirSync(path33.dirname(path33.resolve(opts.output)), { recursive: true });
4339
+ fs28.copyFileSync(saved[0], opts.output);
3431
4340
  logger.success(`Copied to: ${opts.output}`);
3432
4341
  }
3433
4342
  console.log(`
@@ -3448,8 +4357,8 @@ function registerMinimaxCommands(program2) {
3448
4357
  }
3449
4358
 
3450
4359
  // src/providers/openrouter/client.ts
3451
- import fs24 from "fs";
3452
- import path28 from "path";
4360
+ import fs29 from "fs";
4361
+ import path34 from "path";
3453
4362
  init_errors();
3454
4363
  init_http_client();
3455
4364
  var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
@@ -3538,8 +4447,8 @@ async function generateOpenRouterImage(opts) {
3538
4447
  if (!url) continue;
3539
4448
  try {
3540
4449
  const bytes = await fetchBytes(url);
3541
- const dest = path28.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
3542
- fs24.writeFileSync(dest, bytes);
4450
+ const dest = path34.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
4451
+ fs29.writeFileSync(dest, bytes);
3543
4452
  savedFiles.push(dest);
3544
4453
  logger?.success(`Saved: ${dest}`);
3545
4454
  } catch (e) {
@@ -3550,8 +4459,8 @@ async function generateOpenRouterImage(opts) {
3550
4459
  }
3551
4460
  }
3552
4461
  if (output && savedFiles[0]) {
3553
- fs24.mkdirSync(path28.dirname(path28.resolve(output)), { recursive: true });
3554
- 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]));
3555
4464
  }
3556
4465
  return { status: "success", generatedImages: savedFiles, model: usedModel };
3557
4466
  }
@@ -3586,8 +4495,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
3586
4495
  }
3587
4496
 
3588
4497
  // src/providers/openrouter/commands/image-to-image.ts
3589
- import fs25 from "fs";
3590
- import path29 from "path";
4498
+ import fs30 from "fs";
4499
+ import path35 from "path";
3591
4500
  init_errors();
3592
4501
  init_http_client();
3593
4502
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
@@ -3674,14 +4583,14 @@ async function runImageToImage(opts) {
3674
4583
  const url = messageImages[i]?.image_url?.url;
3675
4584
  if (!url) continue;
3676
4585
  const bytes = await fetchBytes(url);
3677
- const dest = path29.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
3678
- fs25.writeFileSync(dest, bytes);
4586
+ const dest = path35.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
4587
+ fs30.writeFileSync(dest, bytes);
3679
4588
  saved.push(dest);
3680
4589
  opts.logger.success(`Saved: ${dest}`);
3681
4590
  }
3682
4591
  if (opts.output && saved[0]) {
3683
- fs25.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
3684
- fs25.copyFileSync(saved[0], opts.output);
4592
+ fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4593
+ fs30.copyFileSync(saved[0], opts.output);
3685
4594
  opts.logger.success(`Copied to: ${opts.output}`);
3686
4595
  }
3687
4596
  return saved;
@@ -3690,7 +4599,7 @@ async function runImageToImage(opts) {
3690
4599
  // src/providers/openrouter/video-client.ts
3691
4600
  init_http_client();
3692
4601
  var OPENROUTER_VIDEOS_URL = "https://openrouter.ai/api/v1/videos";
3693
- function authHeaders3(apiKey) {
4602
+ function authHeaders4(apiKey) {
3694
4603
  const headers = {
3695
4604
  Authorization: `Bearer ${apiKey}`,
3696
4605
  "Content-Type": "application/json"
@@ -3705,7 +4614,7 @@ async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
3705
4614
  return httpJson({
3706
4615
  url: OPENROUTER_VIDEOS_URL,
3707
4616
  method: "POST",
3708
- headers: authHeaders3(apiKey),
4617
+ headers: authHeaders4(apiKey),
3709
4618
  body,
3710
4619
  timeoutMs: 12e4
3711
4620
  });
@@ -3713,7 +4622,7 @@ async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
3713
4622
  async function pollVideoJob(jobId, apiKey = requireOpenRouterKey()) {
3714
4623
  return httpJson({
3715
4624
  url: `${OPENROUTER_VIDEOS_URL}/${jobId}`,
3716
- headers: authHeaders3(apiKey)
4625
+ headers: authHeaders4(apiKey)
3717
4626
  });
3718
4627
  }
3719
4628
  function videoContentUrl(jobId) {
@@ -3722,18 +4631,18 @@ function videoContentUrl(jobId) {
3722
4631
  async function listVideoModels(apiKey = requireOpenRouterKey()) {
3723
4632
  return httpJson({
3724
4633
  url: `${OPENROUTER_VIDEOS_URL}/models`,
3725
- headers: authHeaders3(apiKey)
4634
+ headers: authHeaders4(apiKey)
3726
4635
  });
3727
4636
  }
3728
4637
 
3729
4638
  // src/providers/openrouter/commands/video-status.ts
3730
- import fs26 from "fs";
3731
- import path30 from "path";
4639
+ import fs31 from "fs";
4640
+ import path36 from "path";
3732
4641
  async function downloadOpenRouterVideo(opts) {
3733
4642
  const { jobId, logger, source } = opts;
3734
4643
  const apiKey = requireOpenRouterKey();
3735
4644
  const url = videoContentUrl(jobId);
3736
- const dest = path30.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
4645
+ const dest = path36.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
3737
4646
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
3738
4647
  if (!r.ok) {
3739
4648
  logger.error(`Download HTTP ${r.status}`);
@@ -3743,14 +4652,14 @@ async function downloadOpenRouterVideo(opts) {
3743
4652
  logger.error("Empty response body");
3744
4653
  process.exit(1);
3745
4654
  }
3746
- fs26.mkdirSync(path30.dirname(dest), { recursive: true });
3747
- const ws = fs26.createWriteStream(dest);
4655
+ fs31.mkdirSync(path36.dirname(dest), { recursive: true });
4656
+ const ws = fs31.createWriteStream(dest);
3748
4657
  const { pipeline: pipeline2 } = await import("stream/promises");
3749
4658
  await pipeline2(r.body, ws);
3750
4659
  logger.success(`Saved ${dest}`);
3751
4660
  if (opts.output) {
3752
- fs26.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
3753
- fs26.copyFileSync(dest, opts.output);
4661
+ fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
4662
+ fs31.copyFileSync(dest, opts.output);
3754
4663
  logger.success(`Copied to ${opts.output}`);
3755
4664
  }
3756
4665
  await maybeDownloadThumb(source, dest, {
@@ -4012,6 +4921,12 @@ var PROVIDERS = [
4012
4921
  envFallback: "ARK_API_KEY",
4013
4922
  optional: true,
4014
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"
4015
4930
  }
4016
4931
  ];
4017
4932
  function resolveProviderKey(p) {
@@ -4098,8 +5013,8 @@ function registerCheckCommand(program2) {
4098
5013
 
4099
5014
  // src/commands/doc/convert.ts
4100
5015
  init_errors();
4101
- import fs27 from "fs";
4102
- import path31 from "path";
5016
+ import fs32 from "fs";
5017
+ import path37 from "path";
4103
5018
 
4104
5019
  // src/commands/doc/default-prompt.ts
4105
5020
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -4120,18 +5035,18 @@ async function convertDocuments(opts) {
4120
5035
  const model = opts.model ?? DOC_MODEL_DEFAULT;
4121
5036
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
4122
5037
  for (const f of inputs) {
4123
- 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}`);
4124
5039
  }
4125
5040
  let outputPath;
4126
5041
  if (output) {
4127
- outputPath = path31.resolve(output);
5042
+ outputPath = path37.resolve(output);
4128
5043
  } else if (autoName && inputs.length === 1 && inputs[0]) {
4129
- const base = path31.basename(inputs[0], path31.extname(inputs[0]));
4130
- outputPath = path31.join(getOutputDir(), `${base}-extraction.md`);
5044
+ const base = path37.basename(inputs[0], path37.extname(inputs[0]));
5045
+ outputPath = path37.join(getOutputDir(), `${base}-extraction.md`);
4131
5046
  } else {
4132
- outputPath = path31.join(getOutputDir(), "document-extraction.md");
5047
+ outputPath = path37.join(getOutputDir(), "document-extraction.md");
4133
5048
  }
4134
- fs27.mkdirSync(path31.dirname(outputPath), { recursive: true });
5049
+ fs32.mkdirSync(path37.dirname(outputPath), { recursive: true });
4135
5050
  const results = [];
4136
5051
  for (let i = 0; i < inputs.length; i++) {
4137
5052
  const filePath = inputs[i];
@@ -4182,7 +5097,7 @@ async function convertDocuments(opts) {
4182
5097
  `);
4183
5098
  for (const r of results) {
4184
5099
  lines.push(`
4185
- ## ${path31.basename(r.file)}
5100
+ ## ${path37.basename(r.file)}
4186
5101
  `);
4187
5102
  if (r.status === "success" && r.markdown) {
4188
5103
  lines.push(`${r.markdown}
@@ -4195,7 +5110,7 @@ async function convertDocuments(opts) {
4195
5110
  }
4196
5111
  lines.push("\n---\n");
4197
5112
  }
4198
- fs27.writeFileSync(outputPath, lines.join("\n"), "utf8");
5113
+ fs32.writeFileSync(outputPath, lines.join("\n"), "utf8");
4199
5114
  const succeeded = results.filter((r) => r.status === "success").length;
4200
5115
  console.log(`
4201
5116
  ${"=".repeat(50)}`);
@@ -4241,12 +5156,12 @@ function registerDocCommands(program2) {
4241
5156
 
4242
5157
  // src/commands/media/index.ts
4243
5158
  init_errors();
4244
- import fs30 from "fs";
4245
- import path34 from "path";
5159
+ import fs35 from "fs";
5160
+ import path40 from "path";
4246
5161
 
4247
5162
  // src/commands/media/batch.ts
4248
- import fs28 from "fs";
4249
- import path32 from "path";
5163
+ import fs33 from "fs";
5164
+ import path38 from "path";
4250
5165
 
4251
5166
  // src/commands/media/detect-type.ts
4252
5167
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -4442,10 +5357,10 @@ async function batchOptimize(opts) {
4442
5357
  verbose,
4443
5358
  logger
4444
5359
  } = opts;
4445
- fs28.mkdirSync(outputDir, { recursive: true });
4446
- const entries = fs28.readdirSync(inputDir);
5360
+ fs33.mkdirSync(outputDir, { recursive: true });
5361
+ const entries = fs33.readdirSync(inputDir);
4447
5362
  const mediaFiles = entries.filter((f) => {
4448
- const ext = path32.extname(f).toLowerCase();
5363
+ const ext = path38.extname(f).toLowerCase();
4449
5364
  return ALL_EXTS.has(ext);
4450
5365
  });
4451
5366
  if (mediaFiles.length === 0) {
@@ -4456,9 +5371,9 @@ async function batchOptimize(opts) {
4456
5371
  let succeeded = 0;
4457
5372
  const errors = [];
4458
5373
  for (const file of mediaFiles) {
4459
- const inputPath = path32.join(inputDir, file);
4460
- const outputPath = path32.join(outputDir, file);
4461
- const ext = path32.extname(file).toLowerCase();
5374
+ const inputPath = path38.join(inputDir, file);
5375
+ const outputPath = path38.join(outputDir, file);
5376
+ const ext = path38.extname(file).toLowerCase();
4462
5377
  const kind = extToKind(ext);
4463
5378
  logger?.debug(`Processing: ${file}`);
4464
5379
  try {
@@ -4495,8 +5410,8 @@ async function batchOptimize(opts) {
4495
5410
 
4496
5411
  // src/commands/media/split-video.ts
4497
5412
  init_errors();
4498
- import fs29 from "fs";
4499
- import path33 from "path";
5413
+ import fs34 from "fs";
5414
+ import path39 from "path";
4500
5415
  async function splitVideo(opts) {
4501
5416
  await assertFfmpeg();
4502
5417
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -4507,10 +5422,10 @@ async function splitVideo(opts) {
4507
5422
  logger?.info("Video is short enough \u2014 no splitting needed");
4508
5423
  return [input];
4509
5424
  }
4510
- fs29.mkdirSync(outputDir, { recursive: true });
4511
- const ext = path33.extname(input) || ".mp4";
4512
- const baseName = path33.basename(input, ext);
4513
- const outputPattern = path33.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}`);
4514
5429
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
4515
5430
  const args = [
4516
5431
  "-i",
@@ -4529,8 +5444,8 @@ async function splitVideo(opts) {
4529
5444
  await runFfmpeg(args, { verbose, logger });
4530
5445
  const files = [];
4531
5446
  for (let i = 0; i < numChunks + 1; i++) {
4532
- const fname = path33.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
4533
- 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);
4534
5449
  }
4535
5450
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
4536
5451
  return files;
@@ -4544,9 +5459,9 @@ function registerMediaCommands(program2) {
4544
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(
4545
5460
  async (opts) => {
4546
5461
  const logger = createLogger({ verbose: opts.verbose ?? false });
4547
- if (!fs30.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
4548
- fs30.mkdirSync(path34.dirname(path34.resolve(opts.output)), { recursive: true });
4549
- const ext = path34.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();
4550
5465
  const kind = extToKind(ext);
4551
5466
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
4552
5467
  const quality = Number.parseInt(opts.quality, 10);
@@ -4586,7 +5501,7 @@ function registerMediaCommands(program2) {
4586
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(
4587
5502
  async (opts) => {
4588
5503
  const logger = createLogger({ verbose: opts.verbose ?? false });
4589
- 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}`);
4590
5505
  const chunks = await splitVideo({
4591
5506
  input: opts.input,
4592
5507
  outputDir: opts.outputDir,
@@ -4602,7 +5517,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
4602
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(
4603
5518
  async (opts) => {
4604
5519
  const logger = createLogger({ verbose: opts.verbose ?? false });
4605
- if (!fs30.existsSync(opts.inputDir))
5520
+ if (!fs35.existsSync(opts.inputDir))
4606
5521
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
4607
5522
  const result = await batchOptimize({
4608
5523
  inputDir: opts.inputDir,
@@ -4631,6 +5546,7 @@ function registerCommands(program2) {
4631
5546
  registerOpenRouterCommands(program2);
4632
5547
  registerLeonardoCommands(program2);
4633
5548
  registerBytePlusCommands(program2);
5549
+ registerElevenLabsCommands(program2);
4634
5550
  registerMediaCommands(program2);
4635
5551
  registerDocCommands(program2);
4636
5552
  }