@mrgoonie/multix 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -67,8 +67,8 @@ __export(http_client_exports, {
67
67
  fetchBytes: () => fetchBytes,
68
68
  httpJson: () => httpJson
69
69
  });
70
- import fs2 from "fs";
71
- import path2 from "path";
70
+ import fs4 from "fs";
71
+ import path4 from "path";
72
72
  import { pipeline } from "stream/promises";
73
73
  async function httpJson(opts) {
74
74
  const { url, method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
@@ -103,7 +103,7 @@ async function httpJson(opts) {
103
103
  return response.json();
104
104
  }
105
105
  async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
106
- fs2.mkdirSync(path2.dirname(dest), { recursive: true });
106
+ fs4.mkdirSync(path4.dirname(dest), { recursive: true });
107
107
  let response;
108
108
  try {
109
109
  response = await globalThis.fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
@@ -117,7 +117,7 @@ async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
117
117
  if (!response.body) {
118
118
  throw new HttpError(0, "Response body is null", url);
119
119
  }
120
- const fileStream = fs2.createWriteStream(dest);
120
+ const fileStream = fs4.createWriteStream(dest);
121
121
  await pipeline(response.body, fileStream);
122
122
  }
123
123
  async function fetchBytes(urlOrDataUri) {
@@ -155,6 +155,90 @@ var init_http_client = __esm({
155
155
  import { createRequire } from "module";
156
156
  import { Command } from "commander";
157
157
 
158
+ // src/providers/byteplus/commands/generate-3d.ts
159
+ import path5 from "path";
160
+
161
+ // src/core/image-input.ts
162
+ import fs from "fs/promises";
163
+ import path from "path";
164
+ var IMAGE_MIME_BY_EXT = {
165
+ ".jpg": "image/jpeg",
166
+ ".jpeg": "image/jpeg",
167
+ ".png": "image/png",
168
+ ".webp": "image/webp",
169
+ ".gif": "image/gif"
170
+ };
171
+ var VIDEO_MIME_BY_EXT = {
172
+ ".mp4": "video/mp4",
173
+ ".webm": "video/webm",
174
+ ".mov": "video/quicktime",
175
+ ".m4v": "video/x-m4v"
176
+ };
177
+ var AUDIO_MIME_BY_EXT = {
178
+ ".mp3": "audio/mpeg",
179
+ ".wav": "audio/wav",
180
+ ".m4a": "audio/mp4",
181
+ ".flac": "audio/flac",
182
+ ".ogg": "audio/ogg"
183
+ };
184
+ var DEFAULT_SOFT_LIMIT_BYTES = 10 * 1024 * 1024;
185
+ var DEFAULT_HARD_LIMIT_BYTES = 25 * 1024 * 1024;
186
+ function isHttpUrl(input) {
187
+ return /^https?:\/\//i.test(input);
188
+ }
189
+ async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
190
+ const ext = path.extname(filePath).toLowerCase();
191
+ const mime = mimeTable[ext];
192
+ if (!mime) {
193
+ throw new Error(
194
+ `Unsupported ${kindLabel} extension '${ext}'. Allowed: ${Object.keys(mimeTable).join(", ")}`
195
+ );
196
+ }
197
+ const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
198
+ const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
199
+ const buf = await fs.readFile(filePath);
200
+ if (buf.byteLength > hard) {
201
+ throw new Error(
202
+ `${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.`
203
+ );
204
+ }
205
+ if (buf.byteLength > soft) {
206
+ opts.logger?.warn(
207
+ `${kindLabel} payload ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB is large; consider URL input.`
208
+ );
209
+ }
210
+ const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
211
+ return { dataUrl, mime, bytes: buf.byteLength };
212
+ }
213
+ async function resolveImageInput(input, opts = {}) {
214
+ const o = normalizeOpts(opts);
215
+ if (isHttpUrl(input)) return { kind: "url", url: input };
216
+ const { dataUrl, mime, bytes } = await readAsDataUrl(input, IMAGE_MIME_BY_EXT, "image", o);
217
+ return { kind: "data", dataUrl, mime, bytes };
218
+ }
219
+ async function resolveVideoInput(input, opts = {}) {
220
+ const o = normalizeOpts(opts);
221
+ if (isHttpUrl(input)) return { kind: "url", url: input };
222
+ const { dataUrl, mime, bytes } = await readAsDataUrl(input, VIDEO_MIME_BY_EXT, "video", o);
223
+ return { kind: "data", dataUrl, mime, bytes };
224
+ }
225
+ async function resolveAudioInput(input, opts = {}) {
226
+ const o = normalizeOpts(opts);
227
+ if (isHttpUrl(input)) return { kind: "url", url: input };
228
+ const { dataUrl, mime, bytes } = await readAsDataUrl(input, AUDIO_MIME_BY_EXT, "audio", o);
229
+ return { kind: "data", dataUrl, mime, bytes };
230
+ }
231
+ function refUrl(r) {
232
+ return r.kind === "url" ? r.url : r.dataUrl;
233
+ }
234
+ function normalizeOpts(opts) {
235
+ if (!opts) return {};
236
+ if (typeof opts.info === "function" && typeof opts.warn === "function") {
237
+ return { logger: opts };
238
+ }
239
+ return opts;
240
+ }
241
+
158
242
  // src/core/logger.ts
159
243
  var ANSI = {
160
244
  reset: "\x1B[0m",
@@ -200,22 +284,69 @@ ${line}
200
284
  }
201
285
  var defaultLogger = createLogger({ verbose: false });
202
286
 
287
+ // src/core/output-dir.ts
288
+ import fs2 from "fs";
289
+ import path2 from "path";
290
+ var cachedOutputDir;
291
+ function getOutputDir() {
292
+ if (cachedOutputDir) return cachedOutputDir;
293
+ const dir = process.env.MULTIX_OUTPUT_DIR ? path2.resolve(process.env.MULTIX_OUTPUT_DIR) : path2.resolve(process.cwd(), "multix-output");
294
+ fs2.mkdirSync(dir, { recursive: true });
295
+ cachedOutputDir = dir;
296
+ return dir;
297
+ }
298
+
299
+ // src/providers/leonardo/poll.ts
300
+ var PollTimeoutError = class extends Error {
301
+ constructor() {
302
+ super("Polling timed out before the operation completed.");
303
+ this.name = "PollTimeoutError";
304
+ }
305
+ };
306
+ var PollFailedError = class extends Error {
307
+ constructor(value) {
308
+ super("Operation failed.");
309
+ this.value = value;
310
+ this.name = "PollFailedError";
311
+ }
312
+ value;
313
+ };
314
+ async function poll(opts) {
315
+ const interval = opts.intervalMs ?? 4e3;
316
+ const max = opts.maxAttempts ?? 120;
317
+ for (let attempt = 1; attempt <= max; attempt++) {
318
+ if (opts.signal?.aborted) throw new Error("Aborted");
319
+ const value = await opts.fetch();
320
+ opts.onTick?.(attempt, value);
321
+ if (opts.failed?.(value)) throw new PollFailedError(value);
322
+ if (opts.done(value)) return value;
323
+ await new Promise((resolve, reject) => {
324
+ const t = setTimeout(resolve, interval);
325
+ opts.signal?.addEventListener("abort", () => {
326
+ clearTimeout(t);
327
+ reject(new Error("Aborted"));
328
+ });
329
+ });
330
+ }
331
+ throw new PollTimeoutError();
332
+ }
333
+
203
334
  // src/core/env-loader.ts
204
- import fs from "fs";
335
+ import fs3 from "fs";
205
336
  import os from "os";
206
- import path from "path";
337
+ import path3 from "path";
207
338
  import { config as dotenvConfig } from "dotenv";
208
339
  var loaded = false;
209
340
  function loadEnv() {
210
341
  if (loaded) return;
211
342
  loaded = true;
212
- const cwdEnv = path.resolve(process.cwd(), ".env");
213
- if (fs.existsSync(cwdEnv)) {
343
+ const cwdEnv = path3.resolve(process.cwd(), ".env");
344
+ if (fs3.existsSync(cwdEnv)) {
214
345
  dotenvConfig({ path: cwdEnv, override: false });
215
346
  }
216
347
  if (!process.env.MULTIX_DISABLE_HOME_ENV) {
217
- const homeEnv = path.join(os.homedir(), ".multix", ".env");
218
- if (fs.existsSync(homeEnv)) {
348
+ const homeEnv = path3.join(os.homedir(), ".multix", ".env");
349
+ if (fs3.existsSync(homeEnv)) {
219
350
  dotenvConfig({ path: homeEnv, override: false });
220
351
  }
221
352
  }
@@ -236,9 +367,15 @@ init_http_client();
236
367
  var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
237
368
  var DEFAULT_BYTEPLUS_IMAGE_MODEL = "seedream-4-0-250828";
238
369
  var DEFAULT_BYTEPLUS_VIDEO_MODEL = "seedance-2.0";
370
+ var DEFAULT_BYTEPLUS_3D_MODEL = "hyper3d-gen2-260112";
371
+ var BYTEPLUS_3D_MODELS = [
372
+ { id: "hyper3d-gen2-260112", name: "Hyper3D Gen2", family: "hyper3d" },
373
+ { id: "hitem3d-2-0-251223", name: "Hitem3d 2.0", family: "hitem3d" }
374
+ ];
239
375
  var BYTEPLUS_DEFAULTS = {
240
376
  imageModel: process.env.BYTEPLUS_IMAGE_MODEL ?? DEFAULT_BYTEPLUS_IMAGE_MODEL,
241
377
  videoModel: process.env.BYTEPLUS_VIDEO_MODEL ?? DEFAULT_BYTEPLUS_VIDEO_MODEL,
378
+ threeDModel: process.env.BYTEPLUS_3D_MODEL ?? DEFAULT_BYTEPLUS_3D_MODEL,
242
379
  videoResolution: "1080p",
243
380
  videoDuration: 8,
244
381
  videoAspectRatio: "16:9",
@@ -269,9 +406,9 @@ var BytePlusClient = class {
269
406
  }
270
407
  apiKey;
271
408
  baseUrl;
272
- async request(path35, opts = {}) {
409
+ async request(path36, opts = {}) {
273
410
  const base = this.baseUrl.replace(/\/$/, "");
274
- const url = new URL(base + path35);
411
+ const url = new URL(base + path36);
275
412
  if (opts.query) {
276
413
  for (const [k, v] of Object.entries(opts.query)) {
277
414
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -289,119 +426,145 @@ var BytePlusClient = class {
289
426
  timeoutMs: opts.timeoutMs ?? 12e4
290
427
  });
291
428
  }
292
- get(path35, query, logger) {
293
- return this.request(path35, { method: "GET", query, logger });
429
+ get(path36, query, logger) {
430
+ return this.request(path36, { method: "GET", query, logger });
294
431
  }
295
- post(path35, body, logger) {
296
- return this.request(path35, { method: "POST", body, logger });
432
+ post(path36, body, logger) {
433
+ return this.request(path36, { method: "POST", body, logger });
297
434
  }
298
- delete(path35, logger) {
299
- return this.request(path35, { method: "DELETE", logger });
435
+ delete(path36, logger) {
436
+ return this.request(path36, { method: "DELETE", logger });
300
437
  }
301
438
  };
302
439
  function createBytePlusClient() {
303
440
  return new BytePlusClient(requireBytePlusKey(), bytePlusBaseUrl());
304
441
  }
305
442
 
306
- // src/providers/byteplus/generators/image.ts
443
+ // src/providers/byteplus/generators/three-d.ts
307
444
  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
- );
445
+ function buildThreeDTaskBody(opts) {
446
+ const promptText = (opts.prompt ?? "").trim();
447
+ const flagPart = opts.flags?.trim() ?? "";
448
+ const text = [promptText, flagPart].filter((s) => s.length > 0).join(" ");
449
+ const content = [];
450
+ if (text.length > 0) content.push({ type: "text", text });
451
+ for (const img of opts.imageInputs ?? []) {
452
+ content.push({ type: "image_url", image_url: { url: refUrl(img) } });
354
453
  }
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
- );
454
+ if (content.length === 0) {
455
+ throw new Error("3D task requires either a prompt or at least one image input.");
359
456
  }
360
- const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
361
- return { dataUrl, mime, bytes: buf.byteLength };
457
+ const body = { model: opts.model, content };
458
+ if (opts.seed !== void 0) body.seed = opts.seed;
459
+ return body;
362
460
  }
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 };
461
+ async function submitThreeDTask(client, body, logger) {
462
+ return client.post("/contents/generations/tasks", body, logger);
368
463
  }
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 };
464
+ async function getThreeDTaskStatus(client, id, logger) {
465
+ return client.get(`/contents/generations/tasks/${id}`, void 0, logger);
374
466
  }
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 };
467
+ async function waitForThreeDTask(client, id, opts = {}) {
468
+ const intervalMs = opts.intervalMs ?? 4e3;
469
+ const maxAttempts = opts.maxAttempts ?? (opts.waitTimeoutMs ? Math.max(1, Math.ceil(opts.waitTimeoutMs / intervalMs)) : 180);
470
+ return poll({
471
+ fetch: () => getThreeDTaskStatus(client, id, opts.logger),
472
+ done: (v) => v.status === "succeeded",
473
+ failed: (v) => v.status === "failed" || v.status === "cancelled",
474
+ intervalMs,
475
+ maxAttempts,
476
+ onTick: (attempt, v) => opts.logger?.debug(`attempt ${attempt} \u2014 status: ${v.status}`)
477
+ });
380
478
  }
381
- function refUrl(r) {
382
- return r.kind === "url" ? r.url : r.dataUrl;
479
+ function inferModelFileExt(url) {
480
+ const m = url.match(/\.(glb|gltf|obj|fbx|usdz|stl|ply|zip)(?:\?|#|$)/i);
481
+ return m?.[1] ? m[1].toLowerCase() : "glb";
383
482
  }
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;
483
+ async function downloadModelFile(url, outputPath, logger) {
484
+ await downloadFile(url, outputPath);
485
+ logger?.success(`Saved ${outputPath}`);
390
486
  }
391
487
 
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;
488
+ // src/providers/byteplus/commands/generate-3d.ts
489
+ function registerBytePlusGenerate3DCommand(parent) {
490
+ parent.command("generate-3d").alias("3d").description(
491
+ `Generate a 3D model via BytePlus (Hyper3D / Hitem3d). Models: ${BYTEPLUS_3D_MODELS.map((m) => m.id).join(", ")}`
492
+ ).option("--prompt <text>", "Text prompt (text-to-3D or to guide image-to-3D)").option(
493
+ "-m, --model <id>",
494
+ `Model id (default ${BYTEPLUS_DEFAULTS.threeDModel})`,
495
+ BYTEPLUS_DEFAULTS.threeDModel
496
+ ).option("--input-image <path|url...>", "1\u20135 reference images for image-to-3D (path or URL)").option(
497
+ "--flags <raw>",
498
+ "Raw flags appended to the prompt (e.g. '--mesh_mode Raw --hd_texture true' for Hyper3D, '--ff 2 --resolution 1536pro' for Hitem3d)"
499
+ ).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(
500
+ async (opts) => {
501
+ const logger = createLogger({ verbose: opts.verbose ?? false });
502
+ const client = createBytePlusClient();
503
+ const images = opts.inputImage ?? [];
504
+ if (images.length > 5) {
505
+ logger.error("BytePlus 3D supports at most 5 reference images.");
506
+ process.exit(1);
507
+ }
508
+ const imageInputs = await Promise.all(images.map((src) => resolveImageInput(src, logger)));
509
+ if (!opts.prompt && imageInputs.length === 0) {
510
+ logger.error("Provide --prompt and/or --input-image to describe the 3D content.");
511
+ process.exit(1);
512
+ }
513
+ const body = buildThreeDTaskBody({
514
+ model: opts.model,
515
+ prompt: opts.prompt,
516
+ flags: opts.flags,
517
+ seed: opts.seed ? Number.parseInt(opts.seed, 10) : void 0,
518
+ imageInputs
519
+ });
520
+ logger.info(
521
+ `Submitting 3D task (${opts.model}${imageInputs.length > 0 ? `, ${imageInputs.length} image ref${imageInputs.length === 1 ? "" : "s"}` : ""})`
522
+ );
523
+ const submitted = await submitThreeDTask(client, body, logger);
524
+ const taskId = submitted.id;
525
+ logger.debug(`taskId: ${taskId}`);
526
+ if (opts.async) {
527
+ console.log(taskId);
528
+ console.error(`Poll with: multix byteplus status ${taskId} --wait --download`);
529
+ return;
530
+ }
531
+ try {
532
+ const final = await waitForThreeDTask(client, taskId, {
533
+ waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 9e5,
534
+ logger
535
+ });
536
+ const url = final.content?.file_url;
537
+ if (!url) {
538
+ logger.error(`Task succeeded but no file_url in response: ${JSON.stringify(final)}`);
539
+ process.exit(1);
540
+ }
541
+ const ext = inferModelFileExt(url);
542
+ const outPath = opts.output ?? path5.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
543
+ await downloadModelFile(url, outPath, logger);
544
+ console.log(`
545
+ Task: ${taskId}`);
546
+ console.log(`Saved: ${outPath}`);
547
+ } catch (e) {
548
+ if (e instanceof PollTimeoutError) {
549
+ logger.error(
550
+ `Polling timed out \u2014 keep the task and check later: multix byteplus status ${taskId} --wait --download`
551
+ );
552
+ process.exit(1);
553
+ }
554
+ if (e instanceof PollFailedError) {
555
+ logger.error(`Task failed: ${JSON.stringify(e.value)}`);
556
+ process.exit(1);
557
+ }
558
+ throw e;
559
+ }
560
+ }
561
+ );
402
562
  }
403
563
 
404
564
  // src/providers/byteplus/generators/image.ts
565
+ init_http_client();
566
+ import fs5 from "fs";
567
+ import path6 from "path";
405
568
  var ASPECT_TO_SIZE = {
406
569
  "1:1": "2048x2048",
407
570
  "16:9": "2560x1440",
@@ -465,14 +628,14 @@ async function generateSeedream(client, opts) {
465
628
  }
466
629
  const ext = (() => {
467
630
  try {
468
- const e = path5.extname(new URL(url).pathname);
631
+ const e = path6.extname(new URL(url).pathname);
469
632
  return e || ".jpg";
470
633
  } catch {
471
634
  return ".jpg";
472
635
  }
473
636
  })();
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 });
637
+ const filename = opts.output && items.length === 1 ? path6.resolve(opts.output) : path6.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
638
+ fs5.mkdirSync(path6.dirname(filename), { recursive: true });
476
639
  await downloadFile(url, filename);
477
640
  saved.push({ path: filename, url, seed: item.seed });
478
641
  logger?.success(`Saved ${filename}`);
@@ -552,12 +715,12 @@ function collect2(value, prev) {
552
715
  }
553
716
 
554
717
  // src/providers/byteplus/commands/image-to-video.ts
555
- import path7 from "path";
718
+ import path8 from "path";
556
719
 
557
720
  // src/core/video-thumb.ts
558
721
  init_http_client();
559
722
  import fs6 from "fs";
560
- import path6 from "path";
723
+ import path7 from "path";
561
724
  var THUMB_KEYS = /* @__PURE__ */ new Set([
562
725
  "cover_image_url",
563
726
  "coverImageUrl",
@@ -607,9 +770,9 @@ function detectThumbUrl(obj) {
607
770
  async function downloadThumbBeside(thumbUrl, videoPath, logger) {
608
771
  const match = thumbUrl.match(IMAGE_EXT_RE);
609
772
  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}`);
773
+ const dir = path7.dirname(videoPath);
774
+ const base = path7.basename(videoPath, path7.extname(videoPath));
775
+ const dest = path7.join(dir, `${base}_thumb.${ext}`);
613
776
  try {
614
777
  await downloadFile(thumbUrl, dest);
615
778
  logger?.success(`Saved thumb ${dest}`);
@@ -628,10 +791,10 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
628
791
  }
629
792
  const saved = await downloadThumbBeside(url, videoPath, opts.logger);
630
793
  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}`);
794
+ const copyExt = path7.extname(saved);
795
+ const copyBase = path7.basename(opts.copyTo, path7.extname(opts.copyTo));
796
+ const copyDir = path7.dirname(path7.resolve(opts.copyTo));
797
+ const copyDest = path7.join(copyDir, `${copyBase}_thumb${copyExt}`);
635
798
  fs6.mkdirSync(copyDir, { recursive: true });
636
799
  fs6.copyFileSync(saved, copyDest);
637
800
  opts.logger?.success(`Copied thumb to ${copyDest}`);
@@ -639,41 +802,6 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
639
802
  return saved;
640
803
  }
641
804
 
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
805
  // src/providers/byteplus/generators/video.ts
678
806
  init_http_client();
679
807
  function buildFlagSuffix(opts) {
@@ -796,7 +924,7 @@ function registerBytePlusImageToVideoCommand(parent) {
796
924
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
797
925
  process.exit(1);
798
926
  }
799
- const outPath = opts.output ?? path7.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
927
+ const outPath = opts.output ?? path8.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
800
928
  await downloadVideo(url, outPath, logger);
801
929
  await maybeDownloadThumb(final, outPath, {
802
930
  skip: opts.thumb === false,
@@ -822,7 +950,7 @@ Task: ${taskId}`);
822
950
  }
823
951
 
824
952
  // src/providers/byteplus/commands/reference-to-video.ts
825
- import path8 from "path";
953
+ import path9 from "path";
826
954
 
827
955
  // src/providers/byteplus/reference-input.ts
828
956
  var MAX_TOTAL_REFS = 12;
@@ -854,10 +982,10 @@ function toRef(kind, resolved, role) {
854
982
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
855
983
  }
856
984
  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);
985
+ const { path: path36, role } = parseRefSpec(spec);
986
+ if (kind === "image") return toRef(kind, await resolveImageInput(path36, logger), role);
987
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path36, logger), role);
988
+ return toRef(kind, await resolveAudioInput(path36, logger), role);
861
989
  }
862
990
  async function resolveRefSpecs(specs, logger) {
863
991
  const out = [];
@@ -954,7 +1082,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
954
1082
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
955
1083
  process.exit(1);
956
1084
  }
957
- const outPath = opts.output ?? path8.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1085
+ const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
958
1086
  await downloadVideo(url, outPath, logger);
959
1087
  await maybeDownloadThumb(final, outPath, {
960
1088
  skip: opts.thumb === false,
@@ -980,7 +1108,7 @@ Task: ${taskId}`);
980
1108
  }
981
1109
 
982
1110
  // src/providers/byteplus/commands/status.ts
983
- import path9 from "path";
1111
+ import path10 from "path";
984
1112
  function registerBytePlusStatusCommand(parent) {
985
1113
  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
1114
  async (taskId, opts) => {
@@ -994,19 +1122,27 @@ function registerBytePlusStatusCommand(parent) {
994
1122
  }) : await getVideoTaskStatus(client, taskId, logger);
995
1123
  console.log(JSON.stringify(status, null, 2));
996
1124
  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");
1125
+ const content = status.content;
1126
+ const videoUrl = content?.video_url;
1127
+ const fileUrl = content?.file_url;
1128
+ if (videoUrl) {
1129
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1130
+ await downloadVideo(videoUrl, outPath, logger);
1131
+ await maybeDownloadThumb(status, outPath, {
1132
+ skip: opts.thumb === false,
1133
+ copyTo: opts.output,
1134
+ logger
1135
+ });
1136
+ console.log(`Saved: ${outPath}`);
1137
+ } else if (fileUrl) {
1138
+ const ext = inferModelFileExt(fileUrl);
1139
+ const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
1140
+ await downloadModelFile(fileUrl, outPath, logger);
1141
+ console.log(`Saved: ${outPath}`);
1142
+ } else {
1143
+ logger.error("Task succeeded but no video_url or file_url present");
1000
1144
  process.exit(1);
1001
1145
  }
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
1146
  }
1011
1147
  } catch (e) {
1012
1148
  if (e instanceof PollTimeoutError) {
@@ -1024,7 +1160,7 @@ function registerBytePlusStatusCommand(parent) {
1024
1160
  }
1025
1161
 
1026
1162
  // src/providers/byteplus/commands/video.ts
1027
- import path10 from "path";
1163
+ import path11 from "path";
1028
1164
  function registerBytePlusVideoCommand(parent) {
1029
1165
  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
1166
  "--aspect-ratio <r>",
@@ -1066,7 +1202,7 @@ function registerBytePlusVideoCommand(parent) {
1066
1202
  logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
1067
1203
  process.exit(1);
1068
1204
  }
1069
- const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1205
+ const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1070
1206
  await downloadVideo(url, outPath, logger);
1071
1207
  await maybeDownloadThumb(final, outPath, {
1072
1208
  skip: opts.thumb === false,
@@ -1095,12 +1231,15 @@ Task: ${taskId}`);
1095
1231
 
1096
1232
  // src/providers/byteplus/commands/index.ts
1097
1233
  function registerBytePlusCommands(program2) {
1098
- const byteplus = program2.command("byteplus").description("BytePlus ModelArk: Seedream image generation, Seedance video generation");
1234
+ const byteplus = program2.command("byteplus").description(
1235
+ "BytePlus ModelArk: Seedream image, Seedance video, Hyper3D / Hitem3d 3D generation"
1236
+ );
1099
1237
  registerBytePlusGenerateCommand(byteplus);
1100
1238
  registerBytePlusImageToImageCommand(byteplus);
1101
1239
  registerBytePlusVideoCommand(byteplus);
1102
1240
  registerBytePlusImageToVideoCommand(byteplus);
1103
1241
  registerBytePlusReferenceToVideoCommand(byteplus);
1242
+ registerBytePlusGenerate3DCommand(byteplus);
1104
1243
  registerBytePlusStatusCommand(byteplus);
1105
1244
  }
1106
1245
 
@@ -1110,12 +1249,12 @@ import fs9 from "fs";
1110
1249
 
1111
1250
  // src/providers/gemini/client.ts
1112
1251
  import fs7 from "fs";
1113
- import path12 from "path";
1252
+ import path13 from "path";
1114
1253
  init_errors();
1115
1254
  init_http_client();
1116
1255
 
1117
1256
  // src/providers/gemini/task-resolver.ts
1118
- import path11 from "path";
1257
+ import path12 from "path";
1119
1258
  var MIME_MAP = {
1120
1259
  // Audio
1121
1260
  ".mp3": "audio/mp3",
@@ -1156,7 +1295,7 @@ var MIME_MAP = {
1156
1295
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1157
1296
  };
1158
1297
  function getMimeType(filePath) {
1159
- const ext = path11.extname(filePath).toLowerCase();
1298
+ const ext = path12.extname(filePath).toLowerCase();
1160
1299
  return MIME_MAP[ext] ?? "application/octet-stream";
1161
1300
  }
1162
1301
  function requiresProcessingWait(mimeType) {
@@ -1183,7 +1322,7 @@ async function uploadFile(filePath, opts = {}) {
1183
1322
  const { timeoutMs = 3e5, logger } = opts;
1184
1323
  const mimeType = getMimeType(filePath);
1185
1324
  const fileBytes = fs7.readFileSync(filePath);
1186
- const displayName = path12.basename(filePath);
1325
+ const displayName = path13.basename(filePath);
1187
1326
  const boundary = `multix_${Date.now()}`;
1188
1327
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
1189
1328
  const body = Buffer.concat([
@@ -1331,11 +1470,11 @@ function getDefaultModel(task) {
1331
1470
 
1332
1471
  // src/providers/gemini/result-formatter.ts
1333
1472
  import fs8 from "fs";
1334
- import path13 from "path";
1473
+ import path14 from "path";
1335
1474
  function formatResults(results, format, outputPath) {
1336
1475
  const content = renderFormat(results, format);
1337
1476
  if (outputPath) {
1338
- fs8.mkdirSync(path13.dirname(path13.resolve(outputPath)), { recursive: true });
1477
+ fs8.mkdirSync(path14.dirname(path14.resolve(outputPath)), { recursive: true });
1339
1478
  fs8.writeFileSync(outputPath, content, "utf8");
1340
1479
  } else {
1341
1480
  process.stdout.write(content);
@@ -1499,7 +1638,7 @@ init_errors();
1499
1638
 
1500
1639
  // src/providers/gemini/generators/speech.ts
1501
1640
  import fs11 from "fs";
1502
- import path14 from "path";
1641
+ import path15 from "path";
1503
1642
 
1504
1643
  // src/providers/gemini/voices.ts
1505
1644
  var GEMINI_TTS_VOICES = [
@@ -1582,11 +1721,11 @@ async function generateGeminiSpeech(opts) {
1582
1721
  const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
1583
1722
  const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
1584
1723
  const outDir = getOutputDir();
1585
- const dest = path14.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
1724
+ const dest = path15.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
1586
1725
  fs11.writeFileSync(dest, outBytes);
1587
1726
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
1588
1727
  if (output) {
1589
- fs11.mkdirSync(path14.dirname(path14.resolve(output)), { recursive: true });
1728
+ fs11.mkdirSync(path15.dirname(path15.resolve(output)), { recursive: true });
1590
1729
  fs11.copyFileSync(dest, output);
1591
1730
  logger?.success(`Copied to: ${output}`);
1592
1731
  }
@@ -1687,7 +1826,7 @@ function collectSpeakers(value, prev) {
1687
1826
 
1688
1827
  // src/providers/gemini/commands/generate-video.ts
1689
1828
  import fs12 from "fs";
1690
- import path15 from "path";
1829
+ import path16 from "path";
1691
1830
  init_errors();
1692
1831
  init_http_client();
1693
1832
  var BASE2 = "https://generativelanguage.googleapis.com";
@@ -1724,7 +1863,7 @@ function registerGenerateVideoCommand(parent) {
1724
1863
  if (!fs12.existsSync(imgPath))
1725
1864
  throw new ValidationError(`Reference image not found: ${imgPath}`);
1726
1865
  const data = fs12.readFileSync(imgPath).toString("base64");
1727
- const ext = path15.extname(imgPath).toLowerCase();
1866
+ const ext = path16.extname(imgPath).toLowerCase();
1728
1867
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
1729
1868
  contentParts.push({ inlineData: { mimeType, data } });
1730
1869
  }
@@ -1773,12 +1912,12 @@ function registerGenerateVideoCommand(parent) {
1773
1912
  );
1774
1913
  }
1775
1914
  const outDir = getOutputDir();
1776
- const dest = path15.join(outDir, `veo_generated_${Date.now()}.mp4`);
1915
+ const dest = path16.join(outDir, `veo_generated_${Date.now()}.mp4`);
1777
1916
  const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
1778
1917
  await downloadFile2(videoUri, dest);
1779
1918
  logger.success(`Saved video: ${dest}`);
1780
1919
  if (opts.output) {
1781
- fs12.mkdirSync(path15.dirname(path15.resolve(opts.output)), { recursive: true });
1920
+ fs12.mkdirSync(path16.dirname(path16.resolve(opts.output)), { recursive: true });
1782
1921
  fs12.copyFileSync(dest, opts.output);
1783
1922
  logger.success(`Copied to: ${opts.output}`);
1784
1923
  }
@@ -1799,7 +1938,7 @@ function sleep2(ms) {
1799
1938
  // src/providers/gemini/commands/generate.ts
1800
1939
  init_errors();
1801
1940
  import fs13 from "fs";
1802
- import path16 from "path";
1941
+ import path17 from "path";
1803
1942
  function registerGenerateCommand(parent) {
1804
1943
  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
1944
  async (opts) => {
@@ -1841,7 +1980,7 @@ function registerGenerateCommand(parent) {
1841
1980
  const outDir = getOutputDir();
1842
1981
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
1843
1982
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
1844
- const dest = path16.join(outDir, fname);
1983
+ const dest = path17.join(outDir, fname);
1845
1984
  fs13.writeFileSync(dest, Buffer.from(img.data, "base64"));
1846
1985
  savedFiles.push(dest);
1847
1986
  logger.success(`Saved: ${dest}`);
@@ -1857,7 +1996,7 @@ function registerGenerateCommand(parent) {
1857
1996
  process.exit(1);
1858
1997
  }
1859
1998
  if (opts.output && savedFiles[0]) {
1860
- fs13.mkdirSync(path16.dirname(path16.resolve(opts.output)), { recursive: true });
1999
+ fs13.mkdirSync(path17.dirname(path17.resolve(opts.output)), { recursive: true });
1861
2000
  fs13.copyFileSync(savedFiles[0], opts.output);
1862
2001
  logger.success(`Copied to: ${opts.output}`);
1863
2002
  }
@@ -1870,7 +2009,7 @@ Generated ${savedFiles.length} image(s):`);
1870
2009
 
1871
2010
  // src/providers/gemini/commands/image-to-image.ts
1872
2011
  import fs14 from "fs";
1873
- import path17 from "path";
2012
+ import path18 from "path";
1874
2013
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
1875
2014
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
1876
2015
  function registerGeminiImageToImageCommand(parent) {
@@ -1931,13 +2070,13 @@ function registerGeminiImageToImageCommand(parent) {
1931
2070
  const img = images[i];
1932
2071
  if (!img) continue;
1933
2072
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
1934
- const dest = path17.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
2073
+ const dest = path18.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
1935
2074
  fs14.writeFileSync(dest, Buffer.from(img.data, "base64"));
1936
2075
  saved.push(dest);
1937
2076
  logger.success(`Saved: ${dest}`);
1938
2077
  }
1939
2078
  if (opts.output && saved[0]) {
1940
- fs14.mkdirSync(path17.dirname(path17.resolve(opts.output)), { recursive: true });
2079
+ fs14.mkdirSync(path18.dirname(path18.resolve(opts.output)), { recursive: true });
1941
2080
  fs14.copyFileSync(saved[0], opts.output);
1942
2081
  logger.success(`Copied to: ${opts.output}`);
1943
2082
  }
@@ -1965,7 +2104,7 @@ function sniffImageMime(buf, url) {
1965
2104
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
1966
2105
  return "image/webp";
1967
2106
  }
1968
- const ext = path17.extname(new URL(url).pathname).toLowerCase();
2107
+ const ext = path18.extname(new URL(url).pathname).toLowerCase();
1969
2108
  if (ext === ".png") return "image/png";
1970
2109
  if (ext === ".webp") return "image/webp";
1971
2110
  if (ext === ".gif") return "image/gif";
@@ -1974,7 +2113,7 @@ function sniffImageMime(buf, url) {
1974
2113
 
1975
2114
  // src/providers/gemini/commands/image-to-video.ts
1976
2115
  import fs15 from "fs";
1977
- import path18 from "path";
2116
+ import path19 from "path";
1978
2117
  init_errors();
1979
2118
  init_http_client();
1980
2119
  var BASE3 = "https://generativelanguage.googleapis.com";
@@ -1983,7 +2122,7 @@ function imagePart(imgPath) {
1983
2122
  throw new ValidationError(`Image not found: ${imgPath}`);
1984
2123
  }
1985
2124
  const data = fs15.readFileSync(imgPath).toString("base64");
1986
- const ext = path18.extname(imgPath).toLowerCase();
2125
+ const ext = path19.extname(imgPath).toLowerCase();
1987
2126
  const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
1988
2127
  return { inlineData: { mimeType, data } };
1989
2128
  }
@@ -2056,11 +2195,11 @@ function registerGeminiImageToVideoCommand(parent) {
2056
2195
  "gemini"
2057
2196
  );
2058
2197
  }
2059
- const dest = path18.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
2198
+ const dest = path19.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
2060
2199
  await downloadFile(videoUri, dest);
2061
2200
  logger.success(`Saved video: ${dest}`);
2062
2201
  if (opts.output) {
2063
- fs15.mkdirSync(path18.dirname(path18.resolve(opts.output)), { recursive: true });
2202
+ fs15.mkdirSync(path19.dirname(path19.resolve(opts.output)), { recursive: true });
2064
2203
  fs15.copyFileSync(dest, opts.output);
2065
2204
  logger.success(`Copied to: ${opts.output}`);
2066
2205
  }
@@ -2143,7 +2282,7 @@ function registerGeminiCommands(program2) {
2143
2282
  // src/providers/leonardo/commands/generate.ts
2144
2283
  init_http_client();
2145
2284
  import fs17 from "fs";
2146
- import path19 from "path";
2285
+ import path20 from "path";
2147
2286
 
2148
2287
  // src/providers/leonardo/client.ts
2149
2288
  init_errors();
@@ -2217,11 +2356,11 @@ var LeonardoClient = class {
2217
2356
  }
2218
2357
  apiKey;
2219
2358
  baseUrl;
2220
- async request(path35, opts = {}) {
2359
+ async request(path36, opts = {}) {
2221
2360
  const base = this.baseUrl.replace(/\/$/, "");
2222
- const v2Override = path35.startsWith("v2:");
2361
+ const v2Override = path36.startsWith("v2:");
2223
2362
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
2224
- const finalPath = v2Override ? path35.slice(3) : path35;
2363
+ const finalPath = v2Override ? path36.slice(3) : path36;
2225
2364
  const url = new URL(finalBase + finalPath);
2226
2365
  if (opts.query) {
2227
2366
  for (const [k, v] of Object.entries(opts.query)) {
@@ -2240,14 +2379,14 @@ var LeonardoClient = class {
2240
2379
  timeoutMs: opts.timeoutMs ?? 12e4
2241
2380
  });
2242
2381
  }
2243
- get(path35, query, logger) {
2244
- return this.request(path35, { method: "GET", query, logger });
2382
+ get(path36, query, logger) {
2383
+ return this.request(path36, { method: "GET", query, logger });
2245
2384
  }
2246
- post(path35, body, logger) {
2247
- return this.request(path35, { method: "POST", body, logger });
2385
+ post(path36, body, logger) {
2386
+ return this.request(path36, { method: "POST", body, logger });
2248
2387
  }
2249
- delete(path35, logger) {
2250
- return this.request(path35, { method: "DELETE", logger });
2388
+ delete(path36, logger) {
2389
+ return this.request(path36, { method: "DELETE", logger });
2251
2390
  }
2252
2391
  };
2253
2392
  function createLeonardoClient() {
@@ -2258,7 +2397,7 @@ function createLeonardoClient() {
2258
2397
  function extFromUrl(url, fallback = ".png") {
2259
2398
  try {
2260
2399
  const u = new URL(url);
2261
- const e = path19.extname(u.pathname);
2400
+ const e = path20.extname(u.pathname);
2262
2401
  return e.length > 0 ? e : fallback;
2263
2402
  } catch {
2264
2403
  return fallback;
@@ -2343,7 +2482,7 @@ function registerLeonardoGenerateCommand(parent) {
2343
2482
  if (!img) continue;
2344
2483
  const ext = extFromUrl(img.url);
2345
2484
  const filename = autoFilename(generationId, i + 1, ext);
2346
- const filepath = path19.join(outDir, filename);
2485
+ const filepath = path20.join(outDir, filename);
2347
2486
  try {
2348
2487
  await downloadFile(img.url, filepath);
2349
2488
  saved.push(filepath);
@@ -2353,8 +2492,8 @@ function registerLeonardoGenerateCommand(parent) {
2353
2492
  }
2354
2493
  }
2355
2494
  if (opts.output && saved[0]) {
2356
- const dest = path19.resolve(opts.output);
2357
- fs17.mkdirSync(path19.dirname(dest), { recursive: true });
2495
+ const dest = path20.resolve(opts.output);
2496
+ fs17.mkdirSync(path20.dirname(dest), { recursive: true });
2358
2497
  fs17.copyFileSync(saved[0], dest);
2359
2498
  logger.success(`Copied to ${dest}`);
2360
2499
  }
@@ -2368,10 +2507,10 @@ Generated ${saved.length} image(s):`);
2368
2507
  // src/providers/leonardo/commands/image-to-image.ts
2369
2508
  init_http_client();
2370
2509
  import fs18 from "fs";
2371
- import path20 from "path";
2510
+ import path21 from "path";
2372
2511
  function extFromUrl2(url, fallback = ".png") {
2373
2512
  try {
2374
- return path20.extname(new URL(url).pathname) || fallback;
2513
+ return path21.extname(new URL(url).pathname) || fallback;
2375
2514
  } catch {
2376
2515
  return fallback;
2377
2516
  }
@@ -2433,7 +2572,7 @@ function registerLeonardoImageToImageCommand(parent) {
2433
2572
  if (!img) continue;
2434
2573
  const ext = extFromUrl2(img.url);
2435
2574
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
2436
- const filepath = path20.join(outDir, filename);
2575
+ const filepath = path21.join(outDir, filename);
2437
2576
  try {
2438
2577
  await downloadFile(img.url, filepath);
2439
2578
  saved.push(filepath);
@@ -2443,8 +2582,8 @@ function registerLeonardoImageToImageCommand(parent) {
2443
2582
  }
2444
2583
  }
2445
2584
  if (opts.output && saved[0]) {
2446
- const dest = path20.resolve(opts.output);
2447
- fs18.mkdirSync(path20.dirname(dest), { recursive: true });
2585
+ const dest = path21.resolve(opts.output);
2586
+ fs18.mkdirSync(path21.dirname(dest), { recursive: true });
2448
2587
  fs18.copyFileSync(saved[0], dest);
2449
2588
  logger.success(`Copied to ${dest}`);
2450
2589
  }
@@ -2457,7 +2596,7 @@ Generated ${saved.length} image(s):`);
2457
2596
 
2458
2597
  // src/providers/leonardo/commands/video.ts
2459
2598
  init_http_client();
2460
- import path21 from "path";
2599
+ import path22 from "path";
2461
2600
  function v2DimsFor(resolution) {
2462
2601
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
2463
2602
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -2568,7 +2707,7 @@ async function pollAndDownload(client, jobId, opts) {
2568
2707
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
2569
2708
  process.exit(1);
2570
2709
  }
2571
- const outPath = opts.output ?? path21.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
2710
+ const outPath = opts.output ?? path22.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
2572
2711
  await downloadFile(videoUrl, outPath);
2573
2712
  logger.success(`Saved ${outPath}`);
2574
2713
  await maybeDownloadThumb(gen, outPath, {
@@ -2675,7 +2814,7 @@ function registerLeonardoModelsCommand(parent) {
2675
2814
 
2676
2815
  // src/providers/leonardo/commands/status.ts
2677
2816
  init_http_client();
2678
- import path22 from "path";
2817
+ import path23 from "path";
2679
2818
  var VIDEO_KEYS = ["motionMP4URL", "url"];
2680
2819
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
2681
2820
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -2740,7 +2879,7 @@ function registerLeonardoStatusCommand(parent) {
2740
2879
  const outDir = getOutputDir();
2741
2880
  let idx = 0;
2742
2881
  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`);
2882
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path23.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
2744
2883
  await downloadFile(url, dest);
2745
2884
  logger.success(`Saved ${dest}`);
2746
2885
  await maybeDownloadThumb(gen, dest, {
@@ -2753,7 +2892,7 @@ function registerLeonardoStatusCommand(parent) {
2753
2892
  for (const url of images) {
2754
2893
  const m = url.match(IMAGE_EXT_RE2);
2755
2894
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
2756
- const dest = path22.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
2895
+ const dest = path23.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
2757
2896
  await downloadFile(url, dest);
2758
2897
  logger.success(`Saved ${dest}`);
2759
2898
  idx++;
@@ -2911,7 +3050,7 @@ function sleep4(ms) {
2911
3050
  // src/providers/minimax/generators/music.ts
2912
3051
  init_http_client();
2913
3052
  import fs19 from "fs";
2914
- import path23 from "path";
3053
+ import path24 from "path";
2915
3054
  async function generateMinimaxMusic(opts) {
2916
3055
  const {
2917
3056
  apiKey,
@@ -2956,7 +3095,7 @@ async function generateMinimaxMusic(opts) {
2956
3095
  }
2957
3096
  const durationMs = resp.extra_info?.music_duration ?? 0;
2958
3097
  const outDir = getOutputDir();
2959
- const dest = path23.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3098
+ const dest = path24.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
2960
3099
  try {
2961
3100
  if (audioData.startsWith("http")) {
2962
3101
  const bytes = await fetchBytes(audioData);
@@ -2974,7 +3113,7 @@ async function generateMinimaxMusic(opts) {
2974
3113
  const durSec = durationMs / 1e3;
2975
3114
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
2976
3115
  if (output) {
2977
- fs19.mkdirSync(path23.dirname(path23.resolve(output)), { recursive: true });
3116
+ fs19.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
2978
3117
  fs19.copyFileSync(dest, output);
2979
3118
  }
2980
3119
  return { status: "success", generatedAudio: dest, durationMs, model };
@@ -3050,7 +3189,7 @@ init_errors();
3050
3189
 
3051
3190
  // src/providers/minimax/generators/speech.ts
3052
3191
  import fs20 from "fs";
3053
- import path24 from "path";
3192
+ import path25 from "path";
3054
3193
  async function generateMinimaxSpeech(opts) {
3055
3194
  const {
3056
3195
  apiKey,
@@ -3097,11 +3236,11 @@ async function generateMinimaxSpeech(opts) {
3097
3236
  const audioBytes = Buffer.from(audioHex, "hex");
3098
3237
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
3099
3238
  const outDir = getOutputDir();
3100
- const dest = path24.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
3239
+ const dest = path25.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
3101
3240
  fs20.writeFileSync(dest, audioBytes);
3102
3241
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
3103
3242
  if (output) {
3104
- fs20.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3243
+ fs20.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
3105
3244
  fs20.copyFileSync(dest, output);
3106
3245
  }
3107
3246
  return { status: "success", generatedAudio: dest, model };
@@ -3150,7 +3289,7 @@ init_errors();
3150
3289
 
3151
3290
  // src/providers/minimax/generators/video.ts
3152
3291
  import fs21 from "fs";
3153
- import path25 from "path";
3292
+ import path26 from "path";
3154
3293
  async function generateMinimaxVideo(opts) {
3155
3294
  const {
3156
3295
  apiKey,
@@ -3196,7 +3335,7 @@ async function generateMinimaxVideo(opts) {
3196
3335
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
3197
3336
  }
3198
3337
  const outDir = getOutputDir();
3199
- const dest = path25.join(outDir, `minimax_video_${Date.now()}.mp4`);
3338
+ const dest = path26.join(outDir, `minimax_video_${Date.now()}.mp4`);
3200
3339
  try {
3201
3340
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
3202
3341
  } catch (e) {
@@ -3206,7 +3345,7 @@ async function generateMinimaxVideo(opts) {
3206
3345
  const stat = fs21.statSync(dest);
3207
3346
  const fileSizeMb = stat.size / (1024 * 1024);
3208
3347
  if (output) {
3209
- fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
3348
+ fs21.mkdirSync(path26.dirname(path26.resolve(output)), { recursive: true });
3210
3349
  fs21.copyFileSync(dest, output);
3211
3350
  }
3212
3351
  await maybeDownloadThumb(pollResult, dest, {
@@ -3271,7 +3410,7 @@ Generated video: ${result.generatedVideo}`);
3271
3410
  init_errors();
3272
3411
  init_http_client();
3273
3412
  import fs22 from "fs";
3274
- import path26 from "path";
3413
+ import path27 from "path";
3275
3414
  async function generateMinimaxImage(opts) {
3276
3415
  const {
3277
3416
  apiKey,
@@ -3309,7 +3448,7 @@ async function generateMinimaxImage(opts) {
3309
3448
  try {
3310
3449
  const bytes = await fetchBytes(url);
3311
3450
  const fname = `minimax_image_${Date.now()}_${i}.png`;
3312
- const dest = path26.join(outDir, fname);
3451
+ const dest = path27.join(outDir, fname);
3313
3452
  fs22.writeFileSync(dest, bytes);
3314
3453
  savedFiles.push(dest);
3315
3454
  logger?.success(`Saved: ${dest}`);
@@ -3321,7 +3460,7 @@ async function generateMinimaxImage(opts) {
3321
3460
  }
3322
3461
  }
3323
3462
  if (output && savedFiles[0]) {
3324
- fs22.mkdirSync(path26.dirname(path26.resolve(output)), { recursive: true });
3463
+ fs22.mkdirSync(path27.dirname(path27.resolve(output)), { recursive: true });
3325
3464
  fs22.copyFileSync(savedFiles[0], output);
3326
3465
  }
3327
3466
  return { status: "success", generatedImages: savedFiles, model };
@@ -3359,7 +3498,7 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
3359
3498
  init_errors();
3360
3499
  init_http_client();
3361
3500
  import fs23 from "fs";
3362
- import path27 from "path";
3501
+ import path28 from "path";
3363
3502
  function registerMinimaxImageToImageCommand(parent) {
3364
3503
  parent.command("image-to-image").alias("i2i").description(
3365
3504
  "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."
@@ -3414,7 +3553,7 @@ function registerMinimaxImageToImageCommand(parent) {
3414
3553
  if (!url) continue;
3415
3554
  try {
3416
3555
  const bytes = await fetchBytes(url);
3417
- const dest = path27.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
3556
+ const dest = path28.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
3418
3557
  fs23.writeFileSync(dest, bytes);
3419
3558
  saved.push(dest);
3420
3559
  logger.success(`Saved: ${dest}`);
@@ -3426,7 +3565,7 @@ function registerMinimaxImageToImageCommand(parent) {
3426
3565
  }
3427
3566
  }
3428
3567
  if (opts.output && saved[0]) {
3429
- fs23.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
3568
+ fs23.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
3430
3569
  fs23.copyFileSync(saved[0], opts.output);
3431
3570
  logger.success(`Copied to: ${opts.output}`);
3432
3571
  }
@@ -3449,7 +3588,7 @@ function registerMinimaxCommands(program2) {
3449
3588
 
3450
3589
  // src/providers/openrouter/client.ts
3451
3590
  import fs24 from "fs";
3452
- import path28 from "path";
3591
+ import path29 from "path";
3453
3592
  init_errors();
3454
3593
  init_http_client();
3455
3594
  var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
@@ -3538,7 +3677,7 @@ async function generateOpenRouterImage(opts) {
3538
3677
  if (!url) continue;
3539
3678
  try {
3540
3679
  const bytes = await fetchBytes(url);
3541
- const dest = path28.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
3680
+ const dest = path29.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
3542
3681
  fs24.writeFileSync(dest, bytes);
3543
3682
  savedFiles.push(dest);
3544
3683
  logger?.success(`Saved: ${dest}`);
@@ -3550,7 +3689,7 @@ async function generateOpenRouterImage(opts) {
3550
3689
  }
3551
3690
  }
3552
3691
  if (output && savedFiles[0]) {
3553
- fs24.mkdirSync(path28.dirname(path28.resolve(output)), { recursive: true });
3692
+ fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3554
3693
  fs24.writeFileSync(output, fs24.readFileSync(savedFiles[0]));
3555
3694
  }
3556
3695
  return { status: "success", generatedImages: savedFiles, model: usedModel };
@@ -3587,7 +3726,7 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
3587
3726
 
3588
3727
  // src/providers/openrouter/commands/image-to-image.ts
3589
3728
  import fs25 from "fs";
3590
- import path29 from "path";
3729
+ import path30 from "path";
3591
3730
  init_errors();
3592
3731
  init_http_client();
3593
3732
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
@@ -3674,13 +3813,13 @@ async function runImageToImage(opts) {
3674
3813
  const url = messageImages[i]?.image_url?.url;
3675
3814
  if (!url) continue;
3676
3815
  const bytes = await fetchBytes(url);
3677
- const dest = path29.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
3816
+ const dest = path30.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
3678
3817
  fs25.writeFileSync(dest, bytes);
3679
3818
  saved.push(dest);
3680
3819
  opts.logger.success(`Saved: ${dest}`);
3681
3820
  }
3682
3821
  if (opts.output && saved[0]) {
3683
- fs25.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
3822
+ fs25.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
3684
3823
  fs25.copyFileSync(saved[0], opts.output);
3685
3824
  opts.logger.success(`Copied to: ${opts.output}`);
3686
3825
  }
@@ -3728,12 +3867,12 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
3728
3867
 
3729
3868
  // src/providers/openrouter/commands/video-status.ts
3730
3869
  import fs26 from "fs";
3731
- import path30 from "path";
3870
+ import path31 from "path";
3732
3871
  async function downloadOpenRouterVideo(opts) {
3733
3872
  const { jobId, logger, source } = opts;
3734
3873
  const apiKey = requireOpenRouterKey();
3735
3874
  const url = videoContentUrl(jobId);
3736
- const dest = path30.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
3875
+ const dest = path31.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
3737
3876
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
3738
3877
  if (!r.ok) {
3739
3878
  logger.error(`Download HTTP ${r.status}`);
@@ -3743,13 +3882,13 @@ async function downloadOpenRouterVideo(opts) {
3743
3882
  logger.error("Empty response body");
3744
3883
  process.exit(1);
3745
3884
  }
3746
- fs26.mkdirSync(path30.dirname(dest), { recursive: true });
3885
+ fs26.mkdirSync(path31.dirname(dest), { recursive: true });
3747
3886
  const ws = fs26.createWriteStream(dest);
3748
3887
  const { pipeline: pipeline2 } = await import("stream/promises");
3749
3888
  await pipeline2(r.body, ws);
3750
3889
  logger.success(`Saved ${dest}`);
3751
3890
  if (opts.output) {
3752
- fs26.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
3891
+ fs26.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
3753
3892
  fs26.copyFileSync(dest, opts.output);
3754
3893
  logger.success(`Copied to ${opts.output}`);
3755
3894
  }
@@ -4099,7 +4238,7 @@ function registerCheckCommand(program2) {
4099
4238
  // src/commands/doc/convert.ts
4100
4239
  init_errors();
4101
4240
  import fs27 from "fs";
4102
- import path31 from "path";
4241
+ import path32 from "path";
4103
4242
 
4104
4243
  // src/commands/doc/default-prompt.ts
4105
4244
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -4124,14 +4263,14 @@ async function convertDocuments(opts) {
4124
4263
  }
4125
4264
  let outputPath;
4126
4265
  if (output) {
4127
- outputPath = path31.resolve(output);
4266
+ outputPath = path32.resolve(output);
4128
4267
  } 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`);
4268
+ const base = path32.basename(inputs[0], path32.extname(inputs[0]));
4269
+ outputPath = path32.join(getOutputDir(), `${base}-extraction.md`);
4131
4270
  } else {
4132
- outputPath = path31.join(getOutputDir(), "document-extraction.md");
4271
+ outputPath = path32.join(getOutputDir(), "document-extraction.md");
4133
4272
  }
4134
- fs27.mkdirSync(path31.dirname(outputPath), { recursive: true });
4273
+ fs27.mkdirSync(path32.dirname(outputPath), { recursive: true });
4135
4274
  const results = [];
4136
4275
  for (let i = 0; i < inputs.length; i++) {
4137
4276
  const filePath = inputs[i];
@@ -4182,7 +4321,7 @@ async function convertDocuments(opts) {
4182
4321
  `);
4183
4322
  for (const r of results) {
4184
4323
  lines.push(`
4185
- ## ${path31.basename(r.file)}
4324
+ ## ${path32.basename(r.file)}
4186
4325
  `);
4187
4326
  if (r.status === "success" && r.markdown) {
4188
4327
  lines.push(`${r.markdown}
@@ -4242,11 +4381,11 @@ function registerDocCommands(program2) {
4242
4381
  // src/commands/media/index.ts
4243
4382
  init_errors();
4244
4383
  import fs30 from "fs";
4245
- import path34 from "path";
4384
+ import path35 from "path";
4246
4385
 
4247
4386
  // src/commands/media/batch.ts
4248
4387
  import fs28 from "fs";
4249
- import path32 from "path";
4388
+ import path33 from "path";
4250
4389
 
4251
4390
  // src/commands/media/detect-type.ts
4252
4391
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -4445,7 +4584,7 @@ async function batchOptimize(opts) {
4445
4584
  fs28.mkdirSync(outputDir, { recursive: true });
4446
4585
  const entries = fs28.readdirSync(inputDir);
4447
4586
  const mediaFiles = entries.filter((f) => {
4448
- const ext = path32.extname(f).toLowerCase();
4587
+ const ext = path33.extname(f).toLowerCase();
4449
4588
  return ALL_EXTS.has(ext);
4450
4589
  });
4451
4590
  if (mediaFiles.length === 0) {
@@ -4456,9 +4595,9 @@ async function batchOptimize(opts) {
4456
4595
  let succeeded = 0;
4457
4596
  const errors = [];
4458
4597
  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();
4598
+ const inputPath = path33.join(inputDir, file);
4599
+ const outputPath = path33.join(outputDir, file);
4600
+ const ext = path33.extname(file).toLowerCase();
4462
4601
  const kind = extToKind(ext);
4463
4602
  logger?.debug(`Processing: ${file}`);
4464
4603
  try {
@@ -4496,7 +4635,7 @@ async function batchOptimize(opts) {
4496
4635
  // src/commands/media/split-video.ts
4497
4636
  init_errors();
4498
4637
  import fs29 from "fs";
4499
- import path33 from "path";
4638
+ import path34 from "path";
4500
4639
  async function splitVideo(opts) {
4501
4640
  await assertFfmpeg();
4502
4641
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -4508,9 +4647,9 @@ async function splitVideo(opts) {
4508
4647
  return [input];
4509
4648
  }
4510
4649
  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}`);
4650
+ const ext = path34.extname(input) || ".mp4";
4651
+ const baseName = path34.basename(input, ext);
4652
+ const outputPattern = path34.join(outputDir, `${baseName}_chunk_%03d${ext}`);
4514
4653
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
4515
4654
  const args = [
4516
4655
  "-i",
@@ -4529,7 +4668,7 @@ async function splitVideo(opts) {
4529
4668
  await runFfmpeg(args, { verbose, logger });
4530
4669
  const files = [];
4531
4670
  for (let i = 0; i < numChunks + 1; i++) {
4532
- const fname = path33.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
4671
+ const fname = path34.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
4533
4672
  if (fs29.existsSync(fname)) files.push(fname);
4534
4673
  }
4535
4674
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
@@ -4545,8 +4684,8 @@ function registerMediaCommands(program2) {
4545
4684
  async (opts) => {
4546
4685
  const logger = createLogger({ verbose: opts.verbose ?? false });
4547
4686
  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();
4687
+ fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4688
+ const ext = path35.extname(opts.input).toLowerCase();
4550
4689
  const kind = extToKind(ext);
4551
4690
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
4552
4691
  const quality = Number.parseInt(opts.quality, 10);