@mrgoonie/multix 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -531,9 +531,9 @@ var BytePlusClient = class {
531
531
  }
532
532
  apiKey;
533
533
  baseUrl;
534
- async request(path49, opts = {}) {
534
+ async request(path50, opts = {}) {
535
535
  const base = this.baseUrl.replace(/\/$/, "");
536
- const url = new URL(base + path49);
536
+ const url = new URL(base + path50);
537
537
  if (opts.query) {
538
538
  for (const [k, v] of Object.entries(opts.query)) {
539
539
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -551,14 +551,14 @@ var BytePlusClient = class {
551
551
  timeoutMs: opts.timeoutMs ?? 12e4
552
552
  });
553
553
  }
554
- get(path49, query, logger) {
555
- return this.request(path49, { method: "GET", query, logger });
554
+ get(path50, query, logger) {
555
+ return this.request(path50, { method: "GET", query, logger });
556
556
  }
557
- post(path49, body, logger) {
558
- return this.request(path49, { method: "POST", body, logger });
557
+ post(path50, body, logger) {
558
+ return this.request(path50, { method: "POST", body, logger });
559
559
  }
560
- delete(path49, logger) {
561
- return this.request(path49, { method: "DELETE", logger });
560
+ delete(path50, logger) {
561
+ return this.request(path50, { method: "DELETE", logger });
562
562
  }
563
563
  };
564
564
  function createBytePlusClient() {
@@ -1298,10 +1298,10 @@ function toRef(kind, resolved, role) {
1298
1298
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
1299
1299
  }
1300
1300
  async function resolveRefSpec(kind, spec, logger) {
1301
- const { path: path49, role } = parseRefSpec(spec);
1302
- if (kind === "image") return toRef(kind, await resolveImageInput(path49, logger), role);
1303
- if (kind === "video") return toRef(kind, await resolveVideoInput(path49, logger), role);
1304
- return toRef(kind, await resolveAudioInput(path49, logger), role);
1301
+ const { path: path50, role } = parseRefSpec(spec);
1302
+ if (kind === "image") return toRef(kind, await resolveImageInput(path50, logger), role);
1303
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path50, logger), role);
1304
+ return toRef(kind, await resolveAudioInput(path50, logger), role);
1305
1305
  }
1306
1306
  async function resolveRefSpecs(specs, logger) {
1307
1307
  const out = [];
@@ -2757,9 +2757,9 @@ var FalClient = class {
2757
2757
  apiKey;
2758
2758
  baseUrl;
2759
2759
  /** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
2760
- async request(path49, opts = {}) {
2760
+ async request(path50, opts = {}) {
2761
2761
  const base = this.baseUrl.replace(/\/$/, "");
2762
- const suffix = path49.startsWith("/") ? path49 : `/${path49}`;
2762
+ const suffix = path50.startsWith("/") ? path50 : `/${path50}`;
2763
2763
  return this.requestAbsolute(base + suffix, opts);
2764
2764
  }
2765
2765
  /** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
@@ -2776,11 +2776,11 @@ var FalClient = class {
2776
2776
  timeoutMs: opts.timeoutMs ?? 12e4
2777
2777
  });
2778
2778
  }
2779
- get(path49, logger) {
2780
- return this.request(path49, { method: "GET", logger });
2779
+ get(path50, logger) {
2780
+ return this.request(path50, { method: "GET", logger });
2781
2781
  }
2782
- post(path49, body, logger) {
2783
- return this.request(path49, { method: "POST", body, logger });
2782
+ post(path50, body, logger) {
2783
+ return this.request(path50, { method: "POST", body, logger });
2784
2784
  }
2785
2785
  getAbsolute(url, logger) {
2786
2786
  return this.requestAbsolute(url, { method: "GET", logger });
@@ -3141,14 +3141,214 @@ function registerFalCommands(program2) {
3141
3141
  registerFalResultCommand(fal);
3142
3142
  }
3143
3143
 
3144
- // src/providers/gemini/commands/analyze.ts
3144
+ // src/core/stdin-file.ts
3145
3145
  init_errors();
3146
- import fs20 from "fs";
3146
+ import fs18 from "fs";
3147
+ import os2 from "os";
3148
+ import path21 from "path";
3149
+ var STDIN_ARG = "-";
3150
+ var STDIN_LABEL = "<stdin>";
3151
+ var HEAD_BYTES = 4096;
3152
+ var MAX_STDIN_BYTES = 2 * 1024 ** 3;
3153
+ var WAIT_HINT_MS = 2e3;
3154
+ var ascii = (buf, start, end) => buf.toString("latin1", start, end);
3155
+ var HEIF_BRANDS = /* @__PURE__ */ new Set([
3156
+ "heic",
3157
+ "heix",
3158
+ "hevc",
3159
+ "hevx",
3160
+ "heim",
3161
+ "heis",
3162
+ "hevm",
3163
+ "hevs",
3164
+ "mif1",
3165
+ "msf1"
3166
+ ]);
3167
+ var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
3168
+ function isMpegAudioFrame(head) {
3169
+ if (head.length < 4 || head[0] !== 255) return false;
3170
+ const b1 = head[1] ?? 0;
3171
+ const b2 = head[2] ?? 0;
3172
+ if ((b1 & 224) !== 224) return false;
3173
+ const version = b1 >> 3 & 3;
3174
+ const layer = b1 >> 1 & 3;
3175
+ const bitrate = b2 >> 4;
3176
+ const sampleRate = b2 >> 2 & 3;
3177
+ return version !== 1 && layer !== 0 && bitrate !== 15 && bitrate !== 0 && sampleRate !== 3;
3178
+ }
3179
+ function detectExtension(head) {
3180
+ if (head.length >= 3 && head[0] === 239 && head[1] === 187 && head[2] === 191) return ".txt";
3181
+ if (head.length >= 2 && (head[0] === 255 && head[1] === 254 || head[0] === 254 && head[1] === 255)) {
3182
+ return void 0;
3183
+ }
3184
+ if (head.length >= 8 && head.readUInt32BE(0) === 2303741511) return ".png";
3185
+ if (head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255) return ".jpg";
3186
+ if (ascii(head, 0, 4) === "GIF8") return ".gif";
3187
+ if (ascii(head, 0, 5) === "%PDF-") return ".pdf";
3188
+ if (ascii(head, 0, 2) === "BM" && head.length >= 26 && head.readUInt32LE(14) >= 12) return ".bmp";
3189
+ if (ascii(head, 0, 4) === "RIFF") {
3190
+ const kind = ascii(head, 8, 12);
3191
+ if (kind === "WEBP") return ".webp";
3192
+ if (kind === "WAVE") return ".wav";
3193
+ if (kind === "AVI ") return ".avi";
3194
+ }
3195
+ if (ascii(head, 0, 4) === "OggS") return ".ogg";
3196
+ if (ascii(head, 0, 4) === "fLaC") return ".flac";
3197
+ if (ascii(head, 0, 4) === "FORM" && ascii(head, 8, 12) === "AIFF") return ".aiff";
3198
+ if (ascii(head, 0, 3) === "ID3" && (head[3] ?? 0) >= 2 && (head[3] ?? 0) <= 4 && head[4] !== 255) {
3199
+ return ".mp3";
3200
+ }
3201
+ if (ascii(head, 4, 8) === "ftyp") {
3202
+ const brand = ascii(head, 8, 12);
3203
+ if (HEIF_BRANDS.has(brand)) return ".heic";
3204
+ if (AVIF_BRANDS.has(brand)) return void 0;
3205
+ if (brand.startsWith("M4A") || brand.startsWith("M4B")) return ".m4a";
3206
+ if (brand === "qt ") return ".mov";
3207
+ if (brand.startsWith("3g")) return ".3gpp";
3208
+ return ".mp4";
3209
+ }
3210
+ if (head.length >= 4 && head.readUInt32BE(0) === 440786851) {
3211
+ return head.includes("webm", 0, "latin1") ? ".webm" : void 0;
3212
+ }
3213
+ if (head.length >= 2 && head[0] === 255 && ((head[1] ?? 0) & 246) === 240) return ".aac";
3214
+ if (isMpegAudioFrame(head)) return ".mp3";
3215
+ if (looksLikeText(head)) return ".txt";
3216
+ return void 0;
3217
+ }
3218
+ function looksLikeText(head) {
3219
+ if (head.length === 0) return false;
3220
+ for (const byte of head) {
3221
+ if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 12 && byte !== 13 && byte !== 27) {
3222
+ return false;
3223
+ }
3224
+ }
3225
+ try {
3226
+ new TextDecoder("utf-8", { fatal: true }).decode(head, { stream: true });
3227
+ return true;
3228
+ } catch {
3229
+ return false;
3230
+ }
3231
+ }
3232
+ function normalizeExt(ext) {
3233
+ const clean = ext.trim().toLowerCase();
3234
+ if (!/^\.?[a-z0-9]{1,10}$/.test(clean)) {
3235
+ throw new ValidationError(`Invalid --stdin-ext: ${ext} (expected e.g. .mp3 or txt)`);
3236
+ }
3237
+ return clean.startsWith(".") ? clean : `.${clean}`;
3238
+ }
3239
+ var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
3240
+ function trackTempDir(dir) {
3241
+ let done = false;
3242
+ const remove = () => {
3243
+ try {
3244
+ fs18.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
3245
+ } catch {
3246
+ }
3247
+ };
3248
+ const onSignal = (signal) => {
3249
+ dispose();
3250
+ process.kill(process.pid, signal);
3251
+ };
3252
+ const dispose = () => {
3253
+ if (done) return;
3254
+ done = true;
3255
+ process.removeListener("exit", dispose);
3256
+ for (const s of SIGNALS) process.removeListener(s, onSignal);
3257
+ remove();
3258
+ };
3259
+ process.once("exit", dispose);
3260
+ for (const s of SIGNALS) process.once(s, onSignal);
3261
+ return dispose;
3262
+ }
3263
+ async function stdinToTempFile(opts = {}) {
3264
+ const input = opts.input ?? process.stdin;
3265
+ if (input.isTTY) {
3266
+ throw new ValidationError(
3267
+ "`--files -` reads piped input, but stdin is a terminal. Example: cat clip.mp3 | multix gemini transcribe --files -"
3268
+ );
3269
+ }
3270
+ const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
3271
+ const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
3272
+ const dir = fs18.mkdtempSync(path21.join(os2.tmpdir(), "multix-stdin-"));
3273
+ const cleanup = trackTempDir(dir);
3274
+ const rawPath = path21.join(dir, "input");
3275
+ const hint = setTimeout(() => {
3276
+ process.stderr.write("Waiting for data on stdin (`--files -`)... press Ctrl+C to cancel\n");
3277
+ }, WAIT_HINT_MS);
3278
+ hint.unref?.();
3279
+ let head = Buffer.alloc(0);
3280
+ let size = 0;
3281
+ try {
3282
+ const fd = fs18.openSync(rawPath, "w", 384);
3283
+ try {
3284
+ for await (const chunk of input) {
3285
+ clearTimeout(hint);
3286
+ const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
3287
+ if (size + buf.length > maxBytes) {
3288
+ throw new ValidationError(
3289
+ `Piped stdin exceeds ${Math.floor(maxBytes / 1024 ** 2)} MB; save it to a file instead`
3290
+ );
3291
+ }
3292
+ if (head.length < HEAD_BYTES) {
3293
+ head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
3294
+ }
3295
+ let offset = 0;
3296
+ while (offset < buf.length) offset += fs18.writeSync(fd, buf, offset);
3297
+ size += buf.length;
3298
+ }
3299
+ } finally {
3300
+ clearTimeout(hint);
3301
+ fs18.closeSync(fd);
3302
+ }
3303
+ if (size === 0) throw new ValidationError("`--files -` was given but stdin was empty");
3304
+ const detectedExt = forcedExt ?? detectExtension(head);
3305
+ const finalPath = path21.join(dir, `stdin${detectedExt ?? ".bin"}`);
3306
+ fs18.renameSync(rawPath, finalPath);
3307
+ return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
3308
+ } catch (error) {
3309
+ cleanup();
3310
+ throw error;
3311
+ }
3312
+ }
3313
+ function labelError(message, input) {
3314
+ return input.path === input.label ? message : message.split(input.path).join(input.label);
3315
+ }
3316
+ async function resolveFileInputs(files, opts = {}) {
3317
+ const stdinCount = files.filter((f) => f === STDIN_ARG).length;
3318
+ if (stdinCount > 1) throw new ValidationError("`-` (stdin) can only be passed once to --files");
3319
+ if (opts.stdinExt && stdinCount === 0) {
3320
+ throw new ValidationError("--stdin-ext only applies when --files includes `-`");
3321
+ }
3322
+ for (const f of files) {
3323
+ if (f !== STDIN_ARG && !fs18.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3324
+ }
3325
+ if (stdinCount === 0) {
3326
+ return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
3327
+ } };
3328
+ }
3329
+ const stdin = await stdinToTempFile({
3330
+ input: opts.input,
3331
+ ext: opts.stdinExt,
3332
+ maxBytes: opts.maxBytes
3333
+ });
3334
+ if (!stdin.detected) {
3335
+ stdin.cleanup();
3336
+ throw new ValidationError(
3337
+ "Could not detect the type of piped stdin; pass --stdin-ext (e.g. --stdin-ext mp3)"
3338
+ );
3339
+ }
3340
+ return {
3341
+ inputs: files.map(
3342
+ (f) => f === STDIN_ARG ? { path: stdin.path, label: STDIN_LABEL } : { path: f, label: f }
3343
+ ),
3344
+ cleanup: stdin.cleanup
3345
+ };
3346
+ }
3147
3347
 
3148
3348
  // src/providers/gemini/client.ts
3149
3349
  init_errors();
3150
- import fs18 from "fs";
3151
- import path22 from "path";
3350
+ import fs19 from "fs";
3351
+ import path23 from "path";
3152
3352
 
3153
3353
  // src/providers/gemini/key-pool.ts
3154
3354
  init_errors();
@@ -3214,7 +3414,7 @@ async function withGeminiKey(fn, opts = {}) {
3214
3414
  }
3215
3415
 
3216
3416
  // src/providers/gemini/task-resolver.ts
3217
- import path21 from "path";
3417
+ import path22 from "path";
3218
3418
  var MIME_MAP = {
3219
3419
  // Audio
3220
3420
  ".mp3": "audio/mp3",
@@ -3255,7 +3455,7 @@ var MIME_MAP = {
3255
3455
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
3256
3456
  };
3257
3457
  function getMimeType(filePath) {
3258
- const ext = path21.extname(filePath).toLowerCase();
3458
+ const ext = path22.extname(filePath).toLowerCase();
3259
3459
  return MIME_MAP[ext] ?? "application/octet-stream";
3260
3460
  }
3261
3461
  function requiresProcessingWait(mimeType) {
@@ -3278,8 +3478,8 @@ async function uploadFile(filePath, opts = {}) {
3278
3478
  async function uploadFileWithKey(filePath, apiKey, opts) {
3279
3479
  const { timeoutMs = 3e5, logger } = opts;
3280
3480
  const mimeType = getMimeType(filePath);
3281
- const fileBytes = fs18.readFileSync(filePath);
3282
- const displayName = path22.basename(filePath);
3481
+ const fileBytes = fs19.readFileSync(filePath);
3482
+ const displayName = path23.basename(filePath);
3283
3483
  const boundary = `multix_${Date.now()}`;
3284
3484
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
3285
3485
  const body = Buffer.concat([
@@ -3396,12 +3596,12 @@ function extractImages(resp) {
3396
3596
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
3397
3597
  }
3398
3598
  function inlinePart(filePath) {
3399
- const data = fs18.readFileSync(filePath).toString("base64");
3599
+ const data = fs19.readFileSync(filePath).toString("base64");
3400
3600
  const mimeType = getMimeType(filePath);
3401
3601
  return { inlineData: { mimeType, data } };
3402
3602
  }
3403
3603
  function shouldUseFileApi(filePath) {
3404
- const stat = fs18.statSync(filePath);
3604
+ const stat = fs19.statSync(filePath);
3405
3605
  return stat.size > 20 * 1024 * 1024;
3406
3606
  }
3407
3607
  function sleep2(ms) {
@@ -3455,13 +3655,13 @@ function getDefaultModel(task) {
3455
3655
  }
3456
3656
 
3457
3657
  // src/providers/gemini/result-formatter.ts
3458
- import fs19 from "fs";
3459
- import path23 from "path";
3658
+ import fs20 from "fs";
3659
+ import path24 from "path";
3460
3660
  function formatResults(results, format, outputPath) {
3461
3661
  const content = renderFormat(results, format);
3462
3662
  if (outputPath) {
3463
- fs19.mkdirSync(path23.dirname(path23.resolve(outputPath)), { recursive: true });
3464
- fs19.writeFileSync(outputPath, content, "utf8");
3663
+ fs20.mkdirSync(path24.dirname(path24.resolve(outputPath)), { recursive: true });
3664
+ fs20.writeFileSync(outputPath, content, "utf8");
3465
3665
  } else {
3466
3666
  process.stdout.write(content);
3467
3667
  }
@@ -3523,17 +3723,17 @@ ${r.response}
3523
3723
 
3524
3724
  // src/providers/gemini/commands/analyze.ts
3525
3725
  function registerAnalyzeCommand(parent) {
3526
- parent.command("analyze").description("Analyze files with Gemini (images, video, audio, documents)").requiredOption("--files <paths...>", "Input file paths to analyze").option("--prompt <text>", "Analysis prompt", "Analyze this content").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file instead of stdout").option("-v, --verbose", "Verbose logging").action(
3726
+ parent.command("analyze").description("Analyze files with Gemini (images, video, audio, documents)").requiredOption("--files <paths...>", "Input file paths to analyze (use - to read piped stdin)").option("--stdin-ext <ext>", "Extension for piped stdin when auto-detection fails (e.g. mp3)").option("--prompt <text>", "Analysis prompt", "Analyze this content").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file instead of stdout").option("-v, --verbose", "Verbose logging").action(
3527
3727
  async (opts) => {
3528
3728
  const logger = createLogger({ verbose: opts.verbose ?? false });
3529
3729
  const model = opts.model ?? getDefaultModel("analyze");
3530
- for (const f of opts.files) {
3531
- if (!fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3532
- }
3730
+ const { inputs, cleanup } = await resolveFileInputs(opts.files, {
3731
+ stdinExt: opts.stdinExt
3732
+ });
3533
3733
  const results = [];
3534
- for (let i = 0; i < opts.files.length; i++) {
3535
- const filePath = opts.files[i];
3536
- logger.debug(`[${i + 1}/${opts.files.length}] Processing: ${filePath}`);
3734
+ for (let i = 0; i < inputs.length; i++) {
3735
+ const { path: filePath, label } = inputs[i];
3736
+ logger.debug(`[${i + 1}/${inputs.length}] Processing: ${label}`);
3537
3737
  try {
3538
3738
  const resp = await withGeminiKey(
3539
3739
  async (apiKey) => {
@@ -3560,17 +3760,21 @@ function registerAnalyzeCommand(parent) {
3560
3760
  { logger }
3561
3761
  );
3562
3762
  results.push({
3563
- file: filePath,
3763
+ file: label,
3564
3764
  status: "success",
3565
3765
  response: extractText(resp)
3566
3766
  });
3567
3767
  } catch (e) {
3568
- const msg = e instanceof Error ? e.message : String(e);
3569
- logger.error(`Failed: ${filePath} \u2014 ${msg}`);
3570
- results.push({ file: filePath, status: "error", error: msg });
3768
+ const msg = labelError(
3769
+ e instanceof Error ? e.message : String(e),
3770
+ inputs[i]
3771
+ );
3772
+ logger.error(`Failed: ${label} \u2014 ${msg}`);
3773
+ results.push({ file: label, status: "error", error: msg });
3571
3774
  }
3572
3775
  }
3573
3776
  formatResults(results, opts.format, opts.output);
3777
+ cleanup();
3574
3778
  logger.debug(
3575
3779
  `Done: ${results.filter((r) => r.status === "success").length}/${results.length} succeeded`
3576
3780
  );
@@ -3581,21 +3785,19 @@ function registerAnalyzeCommand(parent) {
3581
3785
  }
3582
3786
 
3583
3787
  // src/providers/gemini/commands/extract.ts
3584
- init_errors();
3585
- import fs21 from "fs";
3586
3788
  function registerExtractCommand(parent) {
3587
- 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(
3789
+ parent.command("extract").description("Extract structured information from files with Gemini").requiredOption("--files <paths...>", "Input file paths (use - to read piped stdin)").option("--stdin-ext <ext>", "Extension for piped stdin when auto-detection fails (e.g. mp3)").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(
3588
3790
  async (opts) => {
3589
3791
  const logger = createLogger({ verbose: opts.verbose ?? false });
3590
3792
  const model = opts.model ?? getDefaultModel("extract");
3591
- for (const f of opts.files) {
3592
- if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3593
- }
3793
+ const { inputs, cleanup } = await resolveFileInputs(opts.files, {
3794
+ stdinExt: opts.stdinExt
3795
+ });
3594
3796
  const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
3595
3797
  const results = [];
3596
- for (let i = 0; i < opts.files.length; i++) {
3597
- const filePath = opts.files[i];
3598
- logger.debug(`[${i + 1}/${opts.files.length}] Extracting: ${filePath}`);
3798
+ for (let i = 0; i < inputs.length; i++) {
3799
+ const { path: filePath, label } = inputs[i];
3800
+ logger.debug(`[${i + 1}/${inputs.length}] Extracting: ${label}`);
3599
3801
  try {
3600
3802
  const resp = await withGeminiKey(
3601
3803
  async (apiKey) => {
@@ -3621,14 +3823,18 @@ function registerExtractCommand(parent) {
3621
3823
  },
3622
3824
  { logger }
3623
3825
  );
3624
- results.push({ file: filePath, status: "success", response: extractText(resp) });
3826
+ results.push({ file: label, status: "success", response: extractText(resp) });
3625
3827
  } catch (e) {
3626
- const msg = e instanceof Error ? e.message : String(e);
3627
- logger.error(`Failed: ${filePath} \u2014 ${msg}`);
3628
- results.push({ file: filePath, status: "error", error: msg });
3828
+ const msg = labelError(
3829
+ e instanceof Error ? e.message : String(e),
3830
+ inputs[i]
3831
+ );
3832
+ logger.error(`Failed: ${label} \u2014 ${msg}`);
3833
+ results.push({ file: label, status: "error", error: msg });
3629
3834
  }
3630
3835
  }
3631
3836
  formatResults(results, opts.format, opts.output);
3837
+ cleanup();
3632
3838
  const failed = results.filter((r) => r.status === "error").length;
3633
3839
  if (failed > 0) process.exit(1);
3634
3840
  }
@@ -3639,8 +3845,8 @@ function registerExtractCommand(parent) {
3639
3845
  init_errors();
3640
3846
 
3641
3847
  // src/providers/gemini/generators/speech.ts
3642
- import fs22 from "fs";
3643
- import path24 from "path";
3848
+ import fs21 from "fs";
3849
+ import path25 from "path";
3644
3850
 
3645
3851
  // src/providers/gemini/voices.ts
3646
3852
  var GEMINI_TTS_VOICES = [
@@ -3806,12 +4012,12 @@ function extractWavData(wav) {
3806
4012
  }
3807
4013
  function saveSpeech(outBytes, outputFormat, output, logger) {
3808
4014
  const outDir = getOutputDir();
3809
- const dest = path24.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
3810
- fs22.writeFileSync(dest, outBytes);
4015
+ const dest = path25.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
4016
+ fs21.writeFileSync(dest, outBytes);
3811
4017
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
3812
4018
  if (output) {
3813
- fs22.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3814
- fs22.copyFileSync(dest, output);
4019
+ fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
4020
+ fs21.copyFileSync(dest, output);
3815
4021
  logger?.success(`Copied to: ${output}`);
3816
4022
  }
3817
4023
  return dest;
@@ -3924,11 +4130,11 @@ function collectSpeakers(value, prev) {
3924
4130
 
3925
4131
  // src/providers/gemini/commands/generate-video.ts
3926
4132
  init_errors();
3927
- import fs24 from "fs";
3928
- import path25 from "path";
4133
+ import fs23 from "fs";
4134
+ import path26 from "path";
3929
4135
 
3930
4136
  // src/providers/gemini/veo.ts
3931
- import fs23 from "fs";
4137
+ import fs22 from "fs";
3932
4138
 
3933
4139
  // src/core/async-poll.ts
3934
4140
  init_errors();
@@ -3995,13 +4201,13 @@ var BASE2 = `https://${API_HOST}/v1beta`;
3995
4201
  var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
3996
4202
  var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
3997
4203
  function veoImageFromFile(imgPath) {
3998
- if (!fs23.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
4204
+ if (!fs22.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
3999
4205
  const detected = getMimeType(imgPath);
4000
4206
  const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
4001
4207
  if (!VEO_IMAGE_TYPES.has(mimeType)) {
4002
4208
  throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
4003
4209
  }
4004
- return { bytesBase64Encoded: fs23.readFileSync(imgPath).toString("base64"), mimeType };
4210
+ return { bytesBase64Encoded: fs22.readFileSync(imgPath).toString("base64"), mimeType };
4005
4211
  }
4006
4212
  function veoFramesFromPaths(paths = []) {
4007
4213
  if (paths.length > 2) {
@@ -4126,7 +4332,7 @@ function registerGenerateVideoCommand(parent) {
4126
4332
  logger.debug(`Submitting video generation with ${model}...`);
4127
4333
  logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
4128
4334
  const frames = veoFramesFromPaths(opts.referenceImages);
4129
- const dest = path25.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
4335
+ const dest = path26.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
4130
4336
  const finalOp = await runVeo(
4131
4337
  model,
4132
4338
  {
@@ -4140,8 +4346,8 @@ function registerGenerateVideoCommand(parent) {
4140
4346
  );
4141
4347
  logger.success(`Saved video: ${dest}`);
4142
4348
  if (opts.output) {
4143
- fs24.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
4144
- fs24.copyFileSync(dest, opts.output);
4349
+ fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
4350
+ fs23.copyFileSync(dest, opts.output);
4145
4351
  logger.success(`Copied to: ${opts.output}`);
4146
4352
  }
4147
4353
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4157,8 +4363,8 @@ Generated video: ${dest}`);
4157
4363
 
4158
4364
  // src/providers/gemini/commands/generate.ts
4159
4365
  init_errors();
4160
- import fs25 from "fs";
4161
- import path26 from "path";
4366
+ import fs24 from "fs";
4367
+ import path27 from "path";
4162
4368
  function registerGenerateCommand(parent) {
4163
4369
  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").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
4164
4370
  async (opts) => {
@@ -4203,8 +4409,8 @@ function registerGenerateCommand(parent) {
4203
4409
  const outDir = getOutputDir();
4204
4410
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
4205
4411
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
4206
- const dest = path26.join(outDir, fname);
4207
- fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
4412
+ const dest = path27.join(outDir, fname);
4413
+ fs24.writeFileSync(dest, Buffer.from(img.data, "base64"));
4208
4414
  savedFiles.push(dest);
4209
4415
  logger.success(`Saved: ${dest}`);
4210
4416
  }
@@ -4219,8 +4425,8 @@ function registerGenerateCommand(parent) {
4219
4425
  process.exit(1);
4220
4426
  }
4221
4427
  if (opts.output && savedFiles[0]) {
4222
- fs25.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
4223
- fs25.copyFileSync(savedFiles[0], opts.output);
4428
+ fs24.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
4429
+ fs24.copyFileSync(savedFiles[0], opts.output);
4224
4430
  logger.success(`Copied to: ${opts.output}`);
4225
4431
  }
4226
4432
  const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
@@ -4232,8 +4438,8 @@ Generated ${finalImages.length} image(s):`);
4232
4438
  }
4233
4439
 
4234
4440
  // src/providers/gemini/commands/image-to-image.ts
4235
- import fs26 from "fs";
4236
- import path27 from "path";
4441
+ import fs25 from "fs";
4442
+ import path28 from "path";
4237
4443
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
4238
4444
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
4239
4445
  function registerGeminiImageToImageCommand(parent) {
@@ -4294,14 +4500,14 @@ function registerGeminiImageToImageCommand(parent) {
4294
4500
  const img = images[i];
4295
4501
  if (!img) continue;
4296
4502
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
4297
- const dest = path27.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
4298
- fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
4503
+ const dest = path28.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
4504
+ fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
4299
4505
  saved.push(dest);
4300
4506
  logger.success(`Saved: ${dest}`);
4301
4507
  }
4302
4508
  if (opts.output && saved[0]) {
4303
- fs26.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
4304
- fs26.copyFileSync(saved[0], opts.output);
4509
+ fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4510
+ fs25.copyFileSync(saved[0], opts.output);
4305
4511
  logger.success(`Copied to: ${opts.output}`);
4306
4512
  }
4307
4513
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4329,7 +4535,7 @@ function sniffImageMime(buf, url) {
4329
4535
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
4330
4536
  return "image/webp";
4331
4537
  }
4332
- const ext = path27.extname(new URL(url).pathname).toLowerCase();
4538
+ const ext = path28.extname(new URL(url).pathname).toLowerCase();
4333
4539
  if (ext === ".png") return "image/png";
4334
4540
  if (ext === ".webp") return "image/webp";
4335
4541
  if (ext === ".gif") return "image/gif";
@@ -4338,8 +4544,8 @@ function sniffImageMime(buf, url) {
4338
4544
 
4339
4545
  // src/providers/gemini/commands/image-to-video.ts
4340
4546
  init_errors();
4341
- import fs27 from "fs";
4342
- import path28 from "path";
4547
+ import fs26 from "fs";
4548
+ import path29 from "path";
4343
4549
  function registerGeminiImageToVideoCommand(parent) {
4344
4550
  parent.command("image-to-video <imagePath>").alias("i2v").description("Generate video from a starting image with Veo (image-to-video) [EXPERIMENTAL]").requiredOption("--prompt <text>", "Motion/scene prompt").option("--last-frame <path>", "Optional closing frame image (Veo 3.1)").option("--model <id>", "Veo model id").option("--resolution <res>", "720p|1080p", "1080p").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option("--output <path>", "Copy generated video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
4345
4551
  async (imagePath, opts) => {
@@ -4359,7 +4565,7 @@ function registerGeminiImageToVideoCommand(parent) {
4359
4565
  logger.info(
4360
4566
  "Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
4361
4567
  );
4362
- const dest = path28.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
4568
+ const dest = path29.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
4363
4569
  const finalOp = await runVeo(
4364
4570
  model,
4365
4571
  {
@@ -4374,8 +4580,8 @@ function registerGeminiImageToVideoCommand(parent) {
4374
4580
  );
4375
4581
  logger.success(`Saved video: ${dest}`);
4376
4582
  if (opts.output) {
4377
- fs27.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4378
- fs27.copyFileSync(dest, opts.output);
4583
+ fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
4584
+ fs26.copyFileSync(dest, opts.output);
4379
4585
  logger.success(`Copied to: ${opts.output}`);
4380
4586
  }
4381
4587
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4390,20 +4596,21 @@ Generated video: ${dest}`);
4390
4596
  }
4391
4597
 
4392
4598
  // src/providers/gemini/commands/transcribe.ts
4393
- init_errors();
4394
- import fs28 from "fs";
4395
4599
  function registerTranscribeCommand2(parent) {
4396
- 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(
4600
+ parent.command("transcribe").description("Transcribe audio or video files with Gemini").requiredOption(
4601
+ "--files <paths...>",
4602
+ "Audio/video file paths to transcribe (use - to read piped stdin)"
4603
+ ).option("--stdin-ext <ext>", "Extension for piped stdin when auto-detection fails (e.g. mp3)").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(
4397
4604
  async (opts) => {
4398
4605
  const logger = createLogger({ verbose: opts.verbose ?? false });
4399
4606
  const model = opts.model ?? getDefaultModel("transcribe");
4400
- for (const f of opts.files) {
4401
- if (!fs28.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
4402
- }
4607
+ const { inputs, cleanup } = await resolveFileInputs(opts.files, {
4608
+ stdinExt: opts.stdinExt
4609
+ });
4403
4610
  const results = [];
4404
- for (let i = 0; i < opts.files.length; i++) {
4405
- const filePath = opts.files[i];
4406
- logger.debug(`[${i + 1}/${opts.files.length}] Transcribing: ${filePath}`);
4611
+ for (let i = 0; i < inputs.length; i++) {
4612
+ const { path: filePath, label } = inputs[i];
4613
+ logger.debug(`[${i + 1}/${inputs.length}] Transcribing: ${label}`);
4407
4614
  try {
4408
4615
  const resp = await withGeminiKey(
4409
4616
  async (apiKey) => {
@@ -4430,17 +4637,21 @@ function registerTranscribeCommand2(parent) {
4430
4637
  { logger }
4431
4638
  );
4432
4639
  results.push({
4433
- file: filePath,
4640
+ file: label,
4434
4641
  status: "success",
4435
4642
  response: extractText(resp)
4436
4643
  });
4437
4644
  } catch (e) {
4438
- const msg = e instanceof Error ? e.message : String(e);
4439
- logger.error(`Failed: ${filePath} \u2014 ${msg}`);
4440
- results.push({ file: filePath, status: "error", error: msg });
4645
+ const msg = labelError(
4646
+ e instanceof Error ? e.message : String(e),
4647
+ inputs[i]
4648
+ );
4649
+ logger.error(`Failed: ${label} \u2014 ${msg}`);
4650
+ results.push({ file: label, status: "error", error: msg });
4441
4651
  }
4442
4652
  }
4443
4653
  formatResults(results, opts.format, opts.output);
4654
+ cleanup();
4444
4655
  const failed = results.filter((r) => r.status === "error").length;
4445
4656
  if (failed > 0) process.exit(1);
4446
4657
  }
@@ -4463,8 +4674,8 @@ function registerGeminiCommands(program2) {
4463
4674
  }
4464
4675
 
4465
4676
  // src/providers/leonardo/commands/generate.ts
4466
- import fs29 from "fs";
4467
- import path29 from "path";
4677
+ import fs27 from "fs";
4678
+ import path30 from "path";
4468
4679
 
4469
4680
  // src/providers/leonardo/client.ts
4470
4681
  init_errors();
@@ -4537,11 +4748,11 @@ var LeonardoClient = class {
4537
4748
  }
4538
4749
  apiKey;
4539
4750
  baseUrl;
4540
- async request(path49, opts = {}) {
4751
+ async request(path50, opts = {}) {
4541
4752
  const base = this.baseUrl.replace(/\/$/, "");
4542
- const v2Override = path49.startsWith("v2:");
4753
+ const v2Override = path50.startsWith("v2:");
4543
4754
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
4544
- const finalPath = v2Override ? path49.slice(3) : path49;
4755
+ const finalPath = v2Override ? path50.slice(3) : path50;
4545
4756
  const url = new URL(finalBase + finalPath);
4546
4757
  if (opts.query) {
4547
4758
  for (const [k, v] of Object.entries(opts.query)) {
@@ -4560,14 +4771,14 @@ var LeonardoClient = class {
4560
4771
  timeoutMs: opts.timeoutMs ?? 12e4
4561
4772
  });
4562
4773
  }
4563
- get(path49, query, logger) {
4564
- return this.request(path49, { method: "GET", query, logger });
4774
+ get(path50, query, logger) {
4775
+ return this.request(path50, { method: "GET", query, logger });
4565
4776
  }
4566
- post(path49, body, logger) {
4567
- return this.request(path49, { method: "POST", body, logger });
4777
+ post(path50, body, logger) {
4778
+ return this.request(path50, { method: "POST", body, logger });
4568
4779
  }
4569
- delete(path49, logger) {
4570
- return this.request(path49, { method: "DELETE", logger });
4780
+ delete(path50, logger) {
4781
+ return this.request(path50, { method: "DELETE", logger });
4571
4782
  }
4572
4783
  };
4573
4784
  function createLeonardoClient() {
@@ -4578,7 +4789,7 @@ function createLeonardoClient() {
4578
4789
  function extFromUrl2(url, fallback = ".png") {
4579
4790
  try {
4580
4791
  const u = new URL(url);
4581
- const e = path29.extname(u.pathname);
4792
+ const e = path30.extname(u.pathname);
4582
4793
  return e.length > 0 ? e : fallback;
4583
4794
  } catch {
4584
4795
  return fallback;
@@ -4663,7 +4874,7 @@ function registerLeonardoGenerateCommand(parent) {
4663
4874
  if (!img) continue;
4664
4875
  const ext = extFromUrl2(img.url);
4665
4876
  const filename = autoFilename(generationId, i + 1, ext);
4666
- const filepath = path29.join(outDir, filename);
4877
+ const filepath = path30.join(outDir, filename);
4667
4878
  try {
4668
4879
  await downloadFile(img.url, filepath);
4669
4880
  saved.push(filepath);
@@ -4673,9 +4884,9 @@ function registerLeonardoGenerateCommand(parent) {
4673
4884
  }
4674
4885
  }
4675
4886
  if (opts.output && saved[0]) {
4676
- const dest = path29.resolve(opts.output);
4677
- fs29.mkdirSync(path29.dirname(dest), { recursive: true });
4678
- fs29.copyFileSync(saved[0], dest);
4887
+ const dest = path30.resolve(opts.output);
4888
+ fs27.mkdirSync(path30.dirname(dest), { recursive: true });
4889
+ fs27.copyFileSync(saved[0], dest);
4679
4890
  logger.success(`Copied to ${dest}`);
4680
4891
  }
4681
4892
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4687,11 +4898,11 @@ Generated ${finalImages.length} image(s):`);
4687
4898
  }
4688
4899
 
4689
4900
  // src/providers/leonardo/commands/image-to-image.ts
4690
- import fs30 from "fs";
4691
- import path30 from "path";
4901
+ import fs28 from "fs";
4902
+ import path31 from "path";
4692
4903
  function extFromUrl3(url, fallback = ".png") {
4693
4904
  try {
4694
- return path30.extname(new URL(url).pathname) || fallback;
4905
+ return path31.extname(new URL(url).pathname) || fallback;
4695
4906
  } catch {
4696
4907
  return fallback;
4697
4908
  }
@@ -4753,7 +4964,7 @@ function registerLeonardoImageToImageCommand(parent) {
4753
4964
  if (!img) continue;
4754
4965
  const ext = extFromUrl3(img.url);
4755
4966
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
4756
- const filepath = path30.join(outDir, filename);
4967
+ const filepath = path31.join(outDir, filename);
4757
4968
  try {
4758
4969
  await downloadFile(img.url, filepath);
4759
4970
  saved.push(filepath);
@@ -4763,9 +4974,9 @@ function registerLeonardoImageToImageCommand(parent) {
4763
4974
  }
4764
4975
  }
4765
4976
  if (opts.output && saved[0]) {
4766
- const dest = path30.resolve(opts.output);
4767
- fs30.mkdirSync(path30.dirname(dest), { recursive: true });
4768
- fs30.copyFileSync(saved[0], dest);
4977
+ const dest = path31.resolve(opts.output);
4978
+ fs28.mkdirSync(path31.dirname(dest), { recursive: true });
4979
+ fs28.copyFileSync(saved[0], dest);
4769
4980
  logger.success(`Copied to ${dest}`);
4770
4981
  }
4771
4982
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4777,7 +4988,7 @@ Generated ${finalImages.length} image(s):`);
4777
4988
  }
4778
4989
 
4779
4990
  // src/providers/leonardo/commands/video.ts
4780
- import path31 from "path";
4991
+ import path32 from "path";
4781
4992
  function v2DimsFor(resolution) {
4782
4993
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
4783
4994
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -4888,7 +5099,7 @@ async function pollAndDownload(client, jobId, opts) {
4888
5099
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
4889
5100
  process.exit(1);
4890
5101
  }
4891
- const outPath = opts.output ?? path31.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
5102
+ const outPath = opts.output ?? path32.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
4892
5103
  await downloadFile(videoUrl2, outPath);
4893
5104
  logger.success(`Saved ${outPath}`);
4894
5105
  await maybeDownloadThumb(gen, outPath, {
@@ -4994,7 +5205,7 @@ function registerLeonardoModelsCommand(parent) {
4994
5205
  }
4995
5206
 
4996
5207
  // src/providers/leonardo/commands/status.ts
4997
- import path32 from "path";
5208
+ import path33 from "path";
4998
5209
  var VIDEO_KEYS = ["motionMP4URL", "url"];
4999
5210
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
5000
5211
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -5059,7 +5270,7 @@ function registerLeonardoStatusCommand(parent) {
5059
5270
  const outDir = getOutputDir();
5060
5271
  let idx = 0;
5061
5272
  for (const url of videos) {
5062
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path32.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
5273
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
5063
5274
  await downloadFile(url, dest);
5064
5275
  logger.success(`Saved ${dest}`);
5065
5276
  await maybeDownloadThumb(gen, dest, {
@@ -5072,7 +5283,7 @@ function registerLeonardoStatusCommand(parent) {
5072
5283
  for (const url of images) {
5073
5284
  const m = url.match(IMAGE_EXT_RE2);
5074
5285
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
5075
- const dest = path32.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
5286
+ const dest = path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
5076
5287
  await downloadFile(url, dest);
5077
5288
  logger.success(`Saved ${dest}`);
5078
5289
  idx++;
@@ -5229,8 +5440,8 @@ function sleep3(ms) {
5229
5440
  }
5230
5441
 
5231
5442
  // src/providers/minimax/generators/music.ts
5232
- import fs31 from "fs";
5233
- import path33 from "path";
5443
+ import fs29 from "fs";
5444
+ import path34 from "path";
5234
5445
  async function generateMinimaxMusic(opts) {
5235
5446
  const {
5236
5447
  apiKey,
@@ -5275,14 +5486,14 @@ async function generateMinimaxMusic(opts) {
5275
5486
  }
5276
5487
  const durationMs = resp.extra_info?.music_duration ?? 0;
5277
5488
  const outDir = getOutputDir();
5278
- const dest = path33.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
5489
+ const dest = path34.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
5279
5490
  try {
5280
5491
  if (audioData.startsWith("http")) {
5281
5492
  const bytes = await fetchBytes(audioData);
5282
- fs31.writeFileSync(dest, bytes);
5493
+ fs29.writeFileSync(dest, bytes);
5283
5494
  } else {
5284
5495
  const audioBytes = Buffer.from(audioData, "hex");
5285
- fs31.writeFileSync(dest, audioBytes);
5496
+ fs29.writeFileSync(dest, audioBytes);
5286
5497
  }
5287
5498
  } catch (e) {
5288
5499
  return {
@@ -5293,8 +5504,8 @@ async function generateMinimaxMusic(opts) {
5293
5504
  const durSec = durationMs / 1e3;
5294
5505
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
5295
5506
  if (output) {
5296
- fs31.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
5297
- fs31.copyFileSync(dest, output);
5507
+ fs29.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5508
+ fs29.copyFileSync(dest, output);
5298
5509
  }
5299
5510
  return { status: "success", generatedAudio: dest, durationMs, model };
5300
5511
  }
@@ -5368,8 +5579,8 @@ Generated music: ${result.generatedAudio}`);
5368
5579
  init_errors();
5369
5580
 
5370
5581
  // src/providers/minimax/generators/speech.ts
5371
- import fs32 from "fs";
5372
- import path34 from "path";
5582
+ import fs30 from "fs";
5583
+ import path35 from "path";
5373
5584
  async function generateMinimaxSpeech(opts) {
5374
5585
  const {
5375
5586
  apiKey,
@@ -5416,12 +5627,12 @@ async function generateMinimaxSpeech(opts) {
5416
5627
  const audioBytes = Buffer.from(audioHex, "hex");
5417
5628
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
5418
5629
  const outDir = getOutputDir();
5419
- const dest = path34.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
5420
- fs32.writeFileSync(dest, audioBytes);
5630
+ const dest = path35.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
5631
+ fs30.writeFileSync(dest, audioBytes);
5421
5632
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
5422
5633
  if (output) {
5423
- fs32.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5424
- fs32.copyFileSync(dest, output);
5634
+ fs30.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5635
+ fs30.copyFileSync(dest, output);
5425
5636
  }
5426
5637
  return { status: "success", generatedAudio: dest, model };
5427
5638
  }
@@ -5468,8 +5679,8 @@ Generated audio: ${result.generatedAudio}`);
5468
5679
  init_errors();
5469
5680
 
5470
5681
  // src/providers/minimax/generators/video.ts
5471
- import fs33 from "fs";
5472
- import path35 from "path";
5682
+ import fs31 from "fs";
5683
+ import path36 from "path";
5473
5684
  async function generateMinimaxVideo(opts) {
5474
5685
  const {
5475
5686
  apiKey,
@@ -5515,18 +5726,18 @@ async function generateMinimaxVideo(opts) {
5515
5726
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
5516
5727
  }
5517
5728
  const outDir = getOutputDir();
5518
- const dest = path35.join(outDir, `minimax_video_${Date.now()}.mp4`);
5729
+ const dest = path36.join(outDir, `minimax_video_${Date.now()}.mp4`);
5519
5730
  try {
5520
5731
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
5521
5732
  } catch (e) {
5522
5733
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
5523
5734
  }
5524
5735
  const elapsed = (Date.now() - start) / 1e3;
5525
- const stat = fs33.statSync(dest);
5736
+ const stat = fs31.statSync(dest);
5526
5737
  const fileSizeMb = stat.size / (1024 * 1024);
5527
5738
  if (output) {
5528
- fs33.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5529
- fs33.copyFileSync(dest, output);
5739
+ fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5740
+ fs31.copyFileSync(dest, output);
5530
5741
  }
5531
5742
  await maybeDownloadThumb(pollResult, dest, {
5532
5743
  skip: thumb === false,
@@ -5588,8 +5799,8 @@ Generated video: ${result.generatedVideo}`);
5588
5799
 
5589
5800
  // src/providers/minimax/generators/image.ts
5590
5801
  init_errors();
5591
- import fs34 from "fs";
5592
- import path36 from "path";
5802
+ import fs32 from "fs";
5803
+ import path37 from "path";
5593
5804
  async function generateMinimaxImage(opts) {
5594
5805
  const {
5595
5806
  apiKey,
@@ -5627,8 +5838,8 @@ async function generateMinimaxImage(opts) {
5627
5838
  try {
5628
5839
  const bytes = await fetchBytes(url);
5629
5840
  const fname = `minimax_image_${Date.now()}_${i}.png`;
5630
- const dest = path36.join(outDir, fname);
5631
- fs34.writeFileSync(dest, bytes);
5841
+ const dest = path37.join(outDir, fname);
5842
+ fs32.writeFileSync(dest, bytes);
5632
5843
  savedFiles.push(dest);
5633
5844
  logger?.success(`Saved: ${dest}`);
5634
5845
  } catch (e) {
@@ -5639,8 +5850,8 @@ async function generateMinimaxImage(opts) {
5639
5850
  }
5640
5851
  }
5641
5852
  if (output && savedFiles[0]) {
5642
- fs34.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5643
- fs34.copyFileSync(savedFiles[0], output);
5853
+ fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
5854
+ fs32.copyFileSync(savedFiles[0], output);
5644
5855
  }
5645
5856
  return { status: "success", generatedImages: savedFiles, model };
5646
5857
  }
@@ -5679,8 +5890,8 @@ Generated ${finalImages.length} image(s):`);
5679
5890
 
5680
5891
  // src/providers/minimax/commands/image-to-image.ts
5681
5892
  init_errors();
5682
- import fs35 from "fs";
5683
- import path37 from "path";
5893
+ import fs33 from "fs";
5894
+ import path38 from "path";
5684
5895
  function registerMinimaxImageToImageCommand(parent) {
5685
5896
  parent.command("image-to-image").alias("i2i").description(
5686
5897
  "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."
@@ -5735,8 +5946,8 @@ function registerMinimaxImageToImageCommand(parent) {
5735
5946
  if (!url) continue;
5736
5947
  try {
5737
5948
  const bytes = await fetchBytes(url);
5738
- const dest = path37.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
5739
- fs35.writeFileSync(dest, bytes);
5949
+ const dest = path38.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
5950
+ fs33.writeFileSync(dest, bytes);
5740
5951
  saved.push(dest);
5741
5952
  logger.success(`Saved: ${dest}`);
5742
5953
  } catch (e) {
@@ -5747,8 +5958,8 @@ function registerMinimaxImageToImageCommand(parent) {
5747
5958
  }
5748
5959
  }
5749
5960
  if (opts.output && saved[0]) {
5750
- fs35.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
5751
- fs35.copyFileSync(saved[0], opts.output);
5961
+ fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5962
+ fs33.copyFileSync(saved[0], opts.output);
5752
5963
  logger.success(`Copied to: ${opts.output}`);
5753
5964
  }
5754
5965
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -5882,12 +6093,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
5882
6093
  }
5883
6094
 
5884
6095
  // src/providers/openai/openai-audio.ts
5885
- import fs37 from "fs";
5886
- import path39 from "path";
6096
+ import fs35 from "fs";
6097
+ import path40 from "path";
5887
6098
 
5888
6099
  // src/providers/openai/client.ts
5889
- import fs36 from "fs";
5890
- import path38 from "path";
6100
+ import fs34 from "fs";
6101
+ import path39 from "path";
5891
6102
  init_errors();
5892
6103
  var DEFAULT_TIMEOUT_MS3 = 24e4;
5893
6104
  function requireOpenAIKey() {
@@ -5953,10 +6164,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
5953
6164
  return Buffer.from(await res.arrayBuffer());
5954
6165
  }
5955
6166
  function fileFromPath(filePath) {
5956
- const abs = path38.resolve(filePath);
5957
- if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
5958
- const bytes = fs36.readFileSync(abs);
5959
- return new File([new Uint8Array(bytes)], path38.basename(abs));
6167
+ const abs = path39.resolve(filePath);
6168
+ if (!fs34.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
6169
+ const bytes = fs34.readFileSync(abs);
6170
+ return new File([new Uint8Array(bytes)], path39.basename(abs));
5960
6171
  }
5961
6172
 
5962
6173
  // src/providers/openai/openai-audio.ts
@@ -5995,7 +6206,7 @@ async function resolveAudioReferenceDataUrl(ref) {
5995
6206
  }
5996
6207
  function audioMimeFromReference(ref) {
5997
6208
  const pathname = new URL(ref).pathname;
5998
- const ext = path39.extname(pathname).toLowerCase();
6209
+ const ext = path40.extname(pathname).toLowerCase();
5999
6210
  switch (ext) {
6000
6211
  case ".mp3":
6001
6212
  return "audio/mpeg";
@@ -6014,12 +6225,12 @@ function audioMimeFromReference(ref) {
6014
6225
  async function generateOpenAISpeech(opts) {
6015
6226
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
6016
6227
  const ext = extFromAudioFormat(opts.outputFormat);
6017
- const dest = path39.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
6018
- fs37.writeFileSync(dest, bytes);
6228
+ const dest = path40.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
6229
+ fs35.writeFileSync(dest, bytes);
6019
6230
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
6020
6231
  if (opts.output) {
6021
- fs37.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
6022
- fs37.copyFileSync(dest, opts.output);
6232
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
6233
+ fs35.copyFileSync(dest, opts.output);
6023
6234
  }
6024
6235
  return dest;
6025
6236
  }
@@ -6041,9 +6252,9 @@ async function transcribeOpenAIAudio(opts) {
6041
6252
  );
6042
6253
  const text = typeof data.text === "string" ? data.text : void 0;
6043
6254
  const ext = opts.format === "text" ? "txt" : "json";
6044
- const dest = opts.output ? path39.resolve(opts.output) : path39.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
6045
- fs37.mkdirSync(path39.dirname(dest), { recursive: true });
6046
- fs37.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
6255
+ const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
6256
+ fs35.mkdirSync(path40.dirname(dest), { recursive: true });
6257
+ fs35.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
6047
6258
  opts.logger?.success(`Saved transcript: ${dest}`);
6048
6259
  return { path: dest, text };
6049
6260
  }
@@ -6070,9 +6281,9 @@ Generated audio: ${dest}`);
6070
6281
  }
6071
6282
 
6072
6283
  // src/providers/openai/codex-image-driver.ts
6073
- import fs38 from "fs";
6074
- import os2 from "os";
6075
- import path40 from "path";
6284
+ import fs36 from "fs";
6285
+ import os3 from "os";
6286
+ import path41 from "path";
6076
6287
  import { execa } from "execa";
6077
6288
  init_errors();
6078
6289
  var defaultRunner = async (cmd, args, opts) => {
@@ -6121,12 +6332,12 @@ function sanitizeCodexEnv() {
6121
6332
  async function runCodexImageGeneration(opts) {
6122
6333
  const runner = opts.runner ?? defaultRunner;
6123
6334
  const ext = extFromImageFormat(opts.outputFormat);
6124
- const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
6125
- fs38.mkdirSync(path40.dirname(dest), { recursive: true });
6126
- const tmp = fs38.mkdtempSync(path40.join(os2.tmpdir(), "multix-codex-image-"));
6127
- const schema = path40.join(tmp, "schema.json");
6128
- const result = path40.join(tmp, "result.json");
6129
- fs38.writeFileSync(
6335
+ const dest = opts.output ? path41.resolve(opts.output) : path41.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
6336
+ fs36.mkdirSync(path41.dirname(dest), { recursive: true });
6337
+ const tmp = fs36.mkdtempSync(path41.join(os3.tmpdir(), "multix-codex-image-"));
6338
+ const schema = path41.join(tmp, "schema.json");
6339
+ const result = path41.join(tmp, "result.json");
6340
+ fs36.writeFileSync(
6130
6341
  schema,
6131
6342
  JSON.stringify({
6132
6343
  type: "object",
@@ -6156,7 +6367,7 @@ async function runCodexImageGeneration(opts) {
6156
6367
  if (res.exitCode !== 0) {
6157
6368
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
6158
6369
  }
6159
- if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
6370
+ if (!fs36.existsSync(dest) || fs36.statSync(dest).size === 0) {
6160
6371
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
6161
6372
  }
6162
6373
  return { path: dest };
@@ -6173,8 +6384,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
6173
6384
 
6174
6385
  // src/providers/openai/openai-image.ts
6175
6386
  init_errors();
6176
- import fs39 from "fs";
6177
- import path41 from "path";
6387
+ import fs37 from "fs";
6388
+ import path42 from "path";
6178
6389
  function buildImageGenerationBody(opts) {
6179
6390
  return {
6180
6391
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -6207,14 +6418,14 @@ function saveOpenAIImages(opts) {
6207
6418
  for (let i = 0; i < items.length; i++) {
6208
6419
  const b64 = items[i]?.b64_json;
6209
6420
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
6210
- const dest = path41.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
6211
- fs39.writeFileSync(dest, Buffer.from(b64, "base64"));
6421
+ const dest = path42.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
6422
+ fs37.writeFileSync(dest, Buffer.from(b64, "base64"));
6212
6423
  saved.push(dest);
6213
6424
  opts.logger?.success(`Saved: ${dest}`);
6214
6425
  }
6215
6426
  if (opts.output && saved[0]) {
6216
- fs39.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
6217
- fs39.copyFileSync(saved[0], opts.output);
6427
+ fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
6428
+ fs37.copyFileSync(saved[0], opts.output);
6218
6429
  opts.logger?.success(`Copied to: ${opts.output}`);
6219
6430
  }
6220
6431
  return saved;
@@ -6223,7 +6434,7 @@ async function resolveImageRefBytes(ref) {
6223
6434
  if (isHttpUrl(ref)) {
6224
6435
  const bytes2 = await fetchBytes(ref);
6225
6436
  const pathname = new URL(ref).pathname;
6226
- const filename = path41.basename(pathname) || "reference.png";
6437
+ const filename = path42.basename(pathname) || "reference.png";
6227
6438
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
6228
6439
  }
6229
6440
  const file = fileFromPath(ref);
@@ -6268,7 +6479,7 @@ function sniffImageMime2(buf, name) {
6268
6479
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
6269
6480
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
6270
6481
  return "image/webp";
6271
- const ext = path41.extname(name).toLowerCase();
6482
+ const ext = path42.extname(name).toLowerCase();
6272
6483
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
6273
6484
  if (ext === ".webp") return "image/webp";
6274
6485
  return "image/png";
@@ -6412,8 +6623,8 @@ function registerOpenAICommands(program2) {
6412
6623
  }
6413
6624
 
6414
6625
  // src/providers/openrouter/client.ts
6415
- import fs40 from "fs";
6416
- import path42 from "path";
6626
+ import fs38 from "fs";
6627
+ import path43 from "path";
6417
6628
  init_errors();
6418
6629
 
6419
6630
  // src/providers/openrouter/payload.ts
@@ -6577,8 +6788,8 @@ async function generateOpenRouterImage(opts) {
6577
6788
  if (!url) continue;
6578
6789
  try {
6579
6790
  const bytes = await fetchBytes(url);
6580
- const dest = path42.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
6581
- fs40.writeFileSync(dest, bytes);
6791
+ const dest = path43.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
6792
+ fs38.writeFileSync(dest, bytes);
6582
6793
  savedFiles.push(dest);
6583
6794
  logger?.success(`Saved: ${dest}`);
6584
6795
  } catch (e) {
@@ -6589,8 +6800,8 @@ async function generateOpenRouterImage(opts) {
6589
6800
  }
6590
6801
  }
6591
6802
  if (output && savedFiles[0]) {
6592
- fs40.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
6593
- fs40.writeFileSync(output, fs40.readFileSync(savedFiles[0]));
6803
+ fs38.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
6804
+ fs38.writeFileSync(output, fs38.readFileSync(savedFiles[0]));
6594
6805
  }
6595
6806
  return { status: "success", generatedImages: savedFiles, model: usedModel };
6596
6807
  }
@@ -6631,8 +6842,8 @@ Generated ${finalImages.length} image(s):`);
6631
6842
  }
6632
6843
 
6633
6844
  // src/providers/openrouter/commands/image-to-image.ts
6634
- import fs41 from "fs";
6635
- import path43 from "path";
6845
+ import fs39 from "fs";
6846
+ import path44 from "path";
6636
6847
  init_errors();
6637
6848
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
6638
6849
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
@@ -6724,14 +6935,14 @@ async function runImageToImage(opts) {
6724
6935
  const url = parsed.urls[i];
6725
6936
  if (!url) continue;
6726
6937
  const bytes = await fetchBytes(url);
6727
- const dest = path43.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
6728
- fs41.writeFileSync(dest, bytes);
6938
+ const dest = path44.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
6939
+ fs39.writeFileSync(dest, bytes);
6729
6940
  saved.push(dest);
6730
6941
  opts.logger.success(`Saved: ${dest}`);
6731
6942
  }
6732
6943
  if (opts.output && saved[0]) {
6733
- fs41.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
6734
- fs41.copyFileSync(saved[0], opts.output);
6944
+ fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6945
+ fs39.copyFileSync(saved[0], opts.output);
6735
6946
  opts.logger.success(`Copied to: ${opts.output}`);
6736
6947
  }
6737
6948
  return saved;
@@ -6776,13 +6987,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
6776
6987
  }
6777
6988
 
6778
6989
  // src/providers/openrouter/commands/video-status.ts
6779
- import fs42 from "fs";
6780
- import path44 from "path";
6990
+ import fs40 from "fs";
6991
+ import path45 from "path";
6781
6992
  async function downloadOpenRouterVideo(opts) {
6782
6993
  const { jobId, logger, source } = opts;
6783
6994
  const apiKey = requireOpenRouterKey();
6784
6995
  const url = videoContentUrl(jobId);
6785
- const dest = path44.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
6996
+ const dest = path45.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
6786
6997
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
6787
6998
  if (!r.ok) {
6788
6999
  logger.error(`Download HTTP ${r.status}`);
@@ -6792,14 +7003,14 @@ async function downloadOpenRouterVideo(opts) {
6792
7003
  logger.error("Empty response body");
6793
7004
  process.exit(1);
6794
7005
  }
6795
- fs42.mkdirSync(path44.dirname(dest), { recursive: true });
6796
- const ws = fs42.createWriteStream(dest);
7006
+ fs40.mkdirSync(path45.dirname(dest), { recursive: true });
7007
+ const ws = fs40.createWriteStream(dest);
6797
7008
  const { pipeline: pipeline2 } = await import("stream/promises");
6798
7009
  await pipeline2(r.body, ws);
6799
7010
  logger.success(`Saved ${dest}`);
6800
7011
  if (opts.output) {
6801
- fs42.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6802
- fs42.copyFileSync(dest, opts.output);
7012
+ fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
7013
+ fs40.copyFileSync(dest, opts.output);
6803
7014
  logger.success(`Copied to ${opts.output}`);
6804
7015
  }
6805
7016
  await maybeDownloadThumb(source, dest, {
@@ -6965,8 +7176,8 @@ function registerOpenRouterCommands(program2) {
6965
7176
  }
6966
7177
 
6967
7178
  // src/commands/check.ts
6968
- import fs43 from "fs";
6969
- import os3 from "os";
7179
+ import fs41 from "fs";
7180
+ import os4 from "os";
6970
7181
 
6971
7182
  // src/commands/check-helpers/binary-check.ts
6972
7183
  import { execa as execa2 } from "execa";
@@ -7144,8 +7355,8 @@ function registerCheckCommand(program2) {
7144
7355
  logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
7145
7356
  }
7146
7357
  if (!legacyClaudeEnvEnabled()) {
7147
- const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os3.homedir();
7148
- const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs43.existsSync(f));
7358
+ const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os4.homedir();
7359
+ const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs41.existsSync(f));
7149
7360
  if (legacy.length > 0) {
7150
7361
  logger.info(
7151
7362
  `Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
@@ -7250,8 +7461,8 @@ function registerCheckCommand(program2) {
7250
7461
 
7251
7462
  // src/commands/doc/convert.ts
7252
7463
  init_errors();
7253
- import fs44 from "fs";
7254
- import path45 from "path";
7464
+ import fs42 from "fs";
7465
+ import path46 from "path";
7255
7466
 
7256
7467
  // src/commands/doc/default-prompt.ts
7257
7468
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -7272,18 +7483,18 @@ async function convertDocuments(opts) {
7272
7483
  const model = opts.model ?? DOC_MODEL_DEFAULT;
7273
7484
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
7274
7485
  for (const f of inputs) {
7275
- if (!fs44.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
7486
+ if (!fs42.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
7276
7487
  }
7277
7488
  let outputPath;
7278
7489
  if (output) {
7279
- outputPath = path45.resolve(output);
7490
+ outputPath = path46.resolve(output);
7280
7491
  } else if (autoName && inputs.length === 1 && inputs[0]) {
7281
- const base = path45.basename(inputs[0], path45.extname(inputs[0]));
7282
- outputPath = path45.join(getOutputDir(), `${base}-extraction.md`);
7492
+ const base = path46.basename(inputs[0], path46.extname(inputs[0]));
7493
+ outputPath = path46.join(getOutputDir(), `${base}-extraction.md`);
7283
7494
  } else {
7284
- outputPath = path45.join(getOutputDir(), "document-extraction.md");
7495
+ outputPath = path46.join(getOutputDir(), "document-extraction.md");
7285
7496
  }
7286
- fs44.mkdirSync(path45.dirname(outputPath), { recursive: true });
7497
+ fs42.mkdirSync(path46.dirname(outputPath), { recursive: true });
7287
7498
  const results = [];
7288
7499
  for (let i = 0; i < inputs.length; i++) {
7289
7500
  const filePath = inputs[i];
@@ -7343,7 +7554,7 @@ async function convertDocuments(opts) {
7343
7554
  `);
7344
7555
  for (const r of results) {
7345
7556
  lines.push(`
7346
- ## ${path45.basename(r.file)}
7557
+ ## ${path46.basename(r.file)}
7347
7558
  `);
7348
7559
  if (r.status === "success" && r.markdown) {
7349
7560
  lines.push(`${r.markdown}
@@ -7356,7 +7567,7 @@ async function convertDocuments(opts) {
7356
7567
  }
7357
7568
  lines.push("\n---\n");
7358
7569
  }
7359
- fs44.writeFileSync(outputPath, lines.join("\n"), "utf8");
7570
+ fs42.writeFileSync(outputPath, lines.join("\n"), "utf8");
7360
7571
  const succeeded = results.filter((r) => r.status === "success").length;
7361
7572
  console.log(`
7362
7573
  ${"=".repeat(50)}`);
@@ -7404,12 +7615,12 @@ function registerDocCommands(program2) {
7404
7615
 
7405
7616
  // src/commands/media/index.ts
7406
7617
  init_errors();
7407
- import fs47 from "fs";
7408
- import path48 from "path";
7618
+ import fs45 from "fs";
7619
+ import path49 from "path";
7409
7620
 
7410
7621
  // src/commands/media/batch.ts
7411
- import fs45 from "fs";
7412
- import path46 from "path";
7622
+ import fs43 from "fs";
7623
+ import path47 from "path";
7413
7624
 
7414
7625
  // src/commands/media/detect-type.ts
7415
7626
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -7605,10 +7816,10 @@ async function batchOptimize(opts) {
7605
7816
  verbose,
7606
7817
  logger
7607
7818
  } = opts;
7608
- fs45.mkdirSync(outputDir, { recursive: true });
7609
- const entries = fs45.readdirSync(inputDir);
7819
+ fs43.mkdirSync(outputDir, { recursive: true });
7820
+ const entries = fs43.readdirSync(inputDir);
7610
7821
  const mediaFiles = entries.filter((f) => {
7611
- const ext = path46.extname(f).toLowerCase();
7822
+ const ext = path47.extname(f).toLowerCase();
7612
7823
  return ALL_EXTS.has(ext);
7613
7824
  });
7614
7825
  if (mediaFiles.length === 0) {
@@ -7619,9 +7830,9 @@ async function batchOptimize(opts) {
7619
7830
  let succeeded = 0;
7620
7831
  const errors = [];
7621
7832
  for (const file of mediaFiles) {
7622
- const inputPath = path46.join(inputDir, file);
7623
- const outputPath = path46.join(outputDir, file);
7624
- const ext = path46.extname(file).toLowerCase();
7833
+ const inputPath = path47.join(inputDir, file);
7834
+ const outputPath = path47.join(outputDir, file);
7835
+ const ext = path47.extname(file).toLowerCase();
7625
7836
  const kind = extToKind(ext);
7626
7837
  logger?.debug(`Processing: ${file}`);
7627
7838
  try {
@@ -7658,8 +7869,8 @@ async function batchOptimize(opts) {
7658
7869
 
7659
7870
  // src/commands/media/split-video.ts
7660
7871
  init_errors();
7661
- import fs46 from "fs";
7662
- import path47 from "path";
7872
+ import fs44 from "fs";
7873
+ import path48 from "path";
7663
7874
  async function splitVideo(opts) {
7664
7875
  await assertFfmpeg();
7665
7876
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -7670,10 +7881,10 @@ async function splitVideo(opts) {
7670
7881
  logger?.info("Video is short enough \u2014 no splitting needed");
7671
7882
  return [input];
7672
7883
  }
7673
- fs46.mkdirSync(outputDir, { recursive: true });
7674
- const ext = path47.extname(input) || ".mp4";
7675
- const baseName = path47.basename(input, ext);
7676
- const outputPattern = path47.join(outputDir, `${baseName}_chunk_%03d${ext}`);
7884
+ fs44.mkdirSync(outputDir, { recursive: true });
7885
+ const ext = path48.extname(input) || ".mp4";
7886
+ const baseName = path48.basename(input, ext);
7887
+ const outputPattern = path48.join(outputDir, `${baseName}_chunk_%03d${ext}`);
7677
7888
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
7678
7889
  const args = [
7679
7890
  "-i",
@@ -7692,8 +7903,8 @@ async function splitVideo(opts) {
7692
7903
  await runFfmpeg(args, { verbose, logger });
7693
7904
  const files = [];
7694
7905
  for (let i = 0; i < numChunks + 1; i++) {
7695
- const fname = path47.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
7696
- if (fs46.existsSync(fname)) files.push(fname);
7906
+ const fname = path48.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
7907
+ if (fs44.existsSync(fname)) files.push(fname);
7697
7908
  }
7698
7909
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
7699
7910
  return files;
@@ -7707,9 +7918,9 @@ function registerMediaCommands(program2) {
7707
7918
  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(
7708
7919
  async (opts) => {
7709
7920
  const logger = createLogger({ verbose: opts.verbose ?? false });
7710
- if (!fs47.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7711
- fs47.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
7712
- const ext = path48.extname(opts.input).toLowerCase();
7921
+ if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7922
+ fs45.mkdirSync(path49.dirname(path49.resolve(opts.output)), { recursive: true });
7923
+ const ext = path49.extname(opts.input).toLowerCase();
7713
7924
  const kind = extToKind(ext);
7714
7925
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
7715
7926
  const quality = Number.parseInt(opts.quality, 10);
@@ -7749,7 +7960,7 @@ function registerMediaCommands(program2) {
7749
7960
  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(
7750
7961
  async (opts) => {
7751
7962
  const logger = createLogger({ verbose: opts.verbose ?? false });
7752
- if (!fs47.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7963
+ if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7753
7964
  const chunks = await splitVideo({
7754
7965
  input: opts.input,
7755
7966
  outputDir: opts.outputDir,
@@ -7765,7 +7976,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
7765
7976
  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(
7766
7977
  async (opts) => {
7767
7978
  const logger = createLogger({ verbose: opts.verbose ?? false });
7768
- if (!fs47.existsSync(opts.inputDir))
7979
+ if (!fs45.existsSync(opts.inputDir))
7769
7980
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
7770
7981
  const result = await batchOptimize({
7771
7982
  inputDir: opts.inputDir,