@mrgoonie/multix 0.8.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
@@ -280,22 +280,78 @@ init_redact();
280
280
  import fs3 from "fs";
281
281
  import os from "os";
282
282
  import path3 from "path";
283
- import { config as dotenvConfig } from "dotenv";
283
+ import { config as dotenvConfig, parse as dotenvParse } from "dotenv";
284
284
  var loaded = false;
285
+ var loadedFiles = [];
286
+ var LEGACY_SKILL_DIRS = ["ai-multimodal", "ak-ai-multimodal"];
287
+ function legacyFilesUnder(root) {
288
+ const claude = path3.join(root, ".claude");
289
+ return [
290
+ ...LEGACY_SKILL_DIRS.map((dir) => path3.join(claude, "skills", dir, ".env")),
291
+ path3.join(claude, "skills", ".env"),
292
+ path3.join(claude, ".env")
293
+ ];
294
+ }
295
+ function samePath(a, b) {
296
+ const norm = (p) => {
297
+ const r = path3.resolve(p);
298
+ return process.platform === "win32" ? r.toLowerCase() : r;
299
+ };
300
+ return norm(a) === norm(b);
301
+ }
302
+ function legacyClaudeEnvFiles(cwd, home) {
303
+ const files = legacyFilesUnder(cwd);
304
+ if (home && !samePath(home, cwd)) files.push(...legacyFilesUnder(home));
305
+ return files;
306
+ }
307
+ function legacyClaudeEnvEnabled(env = process.env) {
308
+ const value = env.MULTIX_LEGACY_CLAUDE_ENV;
309
+ return value !== void 0 && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
310
+ }
311
+ var LEGACY_ALLOWED_NAME = /^[A-Z][A-Z0-9_]*_(API_KEY(_\d+)?|API_TOKEN|ACCOUNT_ID|GATEWAY_ID)$|^(FAL_KEY|ARK_API_KEY)$/;
312
+ function isLegacyAllowedName(name) {
313
+ return LEGACY_ALLOWED_NAME.test(name);
314
+ }
315
+ function isReadableFile(file) {
316
+ try {
317
+ return fs3.statSync(file).isFile();
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+ function loadFile(file) {
323
+ if (!isReadableFile(file)) return;
324
+ const result = dotenvConfig({ path: file, override: false });
325
+ if (!result.error) loadedFiles.push(file);
326
+ }
327
+ function loadLegacyFile(file) {
328
+ if (!isReadableFile(file)) return;
329
+ let parsed;
330
+ try {
331
+ parsed = dotenvParse(fs3.readFileSync(file));
332
+ } catch {
333
+ return;
334
+ }
335
+ for (const [name, value] of Object.entries(parsed)) {
336
+ if (isLegacyAllowedName(name) && process.env[name] === void 0) process.env[name] = value;
337
+ }
338
+ loadedFiles.push(file);
339
+ }
285
340
  function loadEnv() {
286
341
  if (loaded) return;
287
342
  loaded = true;
288
- const cwdEnv = path3.resolve(process.cwd(), ".env");
289
- if (fs3.existsSync(cwdEnv)) {
290
- dotenvConfig({ path: cwdEnv, override: false });
291
- }
292
- if (!process.env.MULTIX_DISABLE_HOME_ENV) {
293
- const homeEnv = path3.join(os.homedir(), ".multix", ".env");
294
- if (fs3.existsSync(homeEnv)) {
295
- dotenvConfig({ path: homeEnv, override: false });
296
- }
343
+ loadedFiles = [];
344
+ const cwd = process.cwd();
345
+ const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os.homedir();
346
+ loadFile(path3.resolve(cwd, ".env"));
347
+ if (home) loadFile(path3.join(home, ".multix", ".env"));
348
+ if (legacyClaudeEnvEnabled()) {
349
+ for (const file of legacyClaudeEnvFiles(cwd, home)) loadLegacyFile(file);
297
350
  }
298
351
  }
352
+ function getLoadedEnvFiles() {
353
+ return [...loadedFiles];
354
+ }
299
355
  function resolveKey(name) {
300
356
  return process.env[name] || void 0;
301
357
  }
@@ -475,9 +531,9 @@ var BytePlusClient = class {
475
531
  }
476
532
  apiKey;
477
533
  baseUrl;
478
- async request(path49, opts = {}) {
534
+ async request(path50, opts = {}) {
479
535
  const base = this.baseUrl.replace(/\/$/, "");
480
- const url = new URL(base + path49);
536
+ const url = new URL(base + path50);
481
537
  if (opts.query) {
482
538
  for (const [k, v] of Object.entries(opts.query)) {
483
539
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -495,14 +551,14 @@ var BytePlusClient = class {
495
551
  timeoutMs: opts.timeoutMs ?? 12e4
496
552
  });
497
553
  }
498
- get(path49, query, logger) {
499
- return this.request(path49, { method: "GET", query, logger });
554
+ get(path50, query, logger) {
555
+ return this.request(path50, { method: "GET", query, logger });
500
556
  }
501
- post(path49, body, logger) {
502
- return this.request(path49, { method: "POST", body, logger });
557
+ post(path50, body, logger) {
558
+ return this.request(path50, { method: "POST", body, logger });
503
559
  }
504
- delete(path49, logger) {
505
- return this.request(path49, { method: "DELETE", logger });
560
+ delete(path50, logger) {
561
+ return this.request(path50, { method: "DELETE", logger });
506
562
  }
507
563
  };
508
564
  function createBytePlusClient() {
@@ -1242,10 +1298,10 @@ function toRef(kind, resolved, role) {
1242
1298
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
1243
1299
  }
1244
1300
  async function resolveRefSpec(kind, spec, logger) {
1245
- const { path: path49, role } = parseRefSpec(spec);
1246
- if (kind === "image") return toRef(kind, await resolveImageInput(path49, logger), role);
1247
- if (kind === "video") return toRef(kind, await resolveVideoInput(path49, logger), role);
1248
- 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);
1249
1305
  }
1250
1306
  async function resolveRefSpecs(specs, logger) {
1251
1307
  const out = [];
@@ -2701,9 +2757,9 @@ var FalClient = class {
2701
2757
  apiKey;
2702
2758
  baseUrl;
2703
2759
  /** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
2704
- async request(path49, opts = {}) {
2760
+ async request(path50, opts = {}) {
2705
2761
  const base = this.baseUrl.replace(/\/$/, "");
2706
- const suffix = path49.startsWith("/") ? path49 : `/${path49}`;
2762
+ const suffix = path50.startsWith("/") ? path50 : `/${path50}`;
2707
2763
  return this.requestAbsolute(base + suffix, opts);
2708
2764
  }
2709
2765
  /** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
@@ -2720,11 +2776,11 @@ var FalClient = class {
2720
2776
  timeoutMs: opts.timeoutMs ?? 12e4
2721
2777
  });
2722
2778
  }
2723
- get(path49, logger) {
2724
- return this.request(path49, { method: "GET", logger });
2779
+ get(path50, logger) {
2780
+ return this.request(path50, { method: "GET", logger });
2725
2781
  }
2726
- post(path49, body, logger) {
2727
- return this.request(path49, { method: "POST", body, logger });
2782
+ post(path50, body, logger) {
2783
+ return this.request(path50, { method: "POST", body, logger });
2728
2784
  }
2729
2785
  getAbsolute(url, logger) {
2730
2786
  return this.requestAbsolute(url, { method: "GET", logger });
@@ -3085,14 +3141,214 @@ function registerFalCommands(program2) {
3085
3141
  registerFalResultCommand(fal);
3086
3142
  }
3087
3143
 
3088
- // src/providers/gemini/commands/analyze.ts
3144
+ // src/core/stdin-file.ts
3089
3145
  init_errors();
3090
- 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
+ }
3091
3347
 
3092
3348
  // src/providers/gemini/client.ts
3093
3349
  init_errors();
3094
- import fs18 from "fs";
3095
- import path22 from "path";
3350
+ import fs19 from "fs";
3351
+ import path23 from "path";
3096
3352
 
3097
3353
  // src/providers/gemini/key-pool.ts
3098
3354
  init_errors();
@@ -3158,7 +3414,7 @@ async function withGeminiKey(fn, opts = {}) {
3158
3414
  }
3159
3415
 
3160
3416
  // src/providers/gemini/task-resolver.ts
3161
- import path21 from "path";
3417
+ import path22 from "path";
3162
3418
  var MIME_MAP = {
3163
3419
  // Audio
3164
3420
  ".mp3": "audio/mp3",
@@ -3199,7 +3455,7 @@ var MIME_MAP = {
3199
3455
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
3200
3456
  };
3201
3457
  function getMimeType(filePath) {
3202
- const ext = path21.extname(filePath).toLowerCase();
3458
+ const ext = path22.extname(filePath).toLowerCase();
3203
3459
  return MIME_MAP[ext] ?? "application/octet-stream";
3204
3460
  }
3205
3461
  function requiresProcessingWait(mimeType) {
@@ -3222,8 +3478,8 @@ async function uploadFile(filePath, opts = {}) {
3222
3478
  async function uploadFileWithKey(filePath, apiKey, opts) {
3223
3479
  const { timeoutMs = 3e5, logger } = opts;
3224
3480
  const mimeType = getMimeType(filePath);
3225
- const fileBytes = fs18.readFileSync(filePath);
3226
- const displayName = path22.basename(filePath);
3481
+ const fileBytes = fs19.readFileSync(filePath);
3482
+ const displayName = path23.basename(filePath);
3227
3483
  const boundary = `multix_${Date.now()}`;
3228
3484
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
3229
3485
  const body = Buffer.concat([
@@ -3340,12 +3596,12 @@ function extractImages(resp) {
3340
3596
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
3341
3597
  }
3342
3598
  function inlinePart(filePath) {
3343
- const data = fs18.readFileSync(filePath).toString("base64");
3599
+ const data = fs19.readFileSync(filePath).toString("base64");
3344
3600
  const mimeType = getMimeType(filePath);
3345
3601
  return { inlineData: { mimeType, data } };
3346
3602
  }
3347
3603
  function shouldUseFileApi(filePath) {
3348
- const stat = fs18.statSync(filePath);
3604
+ const stat = fs19.statSync(filePath);
3349
3605
  return stat.size > 20 * 1024 * 1024;
3350
3606
  }
3351
3607
  function sleep2(ms) {
@@ -3399,13 +3655,13 @@ function getDefaultModel(task) {
3399
3655
  }
3400
3656
 
3401
3657
  // src/providers/gemini/result-formatter.ts
3402
- import fs19 from "fs";
3403
- import path23 from "path";
3658
+ import fs20 from "fs";
3659
+ import path24 from "path";
3404
3660
  function formatResults(results, format, outputPath) {
3405
3661
  const content = renderFormat(results, format);
3406
3662
  if (outputPath) {
3407
- fs19.mkdirSync(path23.dirname(path23.resolve(outputPath)), { recursive: true });
3408
- fs19.writeFileSync(outputPath, content, "utf8");
3663
+ fs20.mkdirSync(path24.dirname(path24.resolve(outputPath)), { recursive: true });
3664
+ fs20.writeFileSync(outputPath, content, "utf8");
3409
3665
  } else {
3410
3666
  process.stdout.write(content);
3411
3667
  }
@@ -3467,17 +3723,17 @@ ${r.response}
3467
3723
 
3468
3724
  // src/providers/gemini/commands/analyze.ts
3469
3725
  function registerAnalyzeCommand(parent) {
3470
- 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(
3471
3727
  async (opts) => {
3472
3728
  const logger = createLogger({ verbose: opts.verbose ?? false });
3473
3729
  const model = opts.model ?? getDefaultModel("analyze");
3474
- for (const f of opts.files) {
3475
- if (!fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3476
- }
3730
+ const { inputs, cleanup } = await resolveFileInputs(opts.files, {
3731
+ stdinExt: opts.stdinExt
3732
+ });
3477
3733
  const results = [];
3478
- for (let i = 0; i < opts.files.length; i++) {
3479
- const filePath = opts.files[i];
3480
- 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}`);
3481
3737
  try {
3482
3738
  const resp = await withGeminiKey(
3483
3739
  async (apiKey) => {
@@ -3504,17 +3760,21 @@ function registerAnalyzeCommand(parent) {
3504
3760
  { logger }
3505
3761
  );
3506
3762
  results.push({
3507
- file: filePath,
3763
+ file: label,
3508
3764
  status: "success",
3509
3765
  response: extractText(resp)
3510
3766
  });
3511
3767
  } catch (e) {
3512
- const msg = e instanceof Error ? e.message : String(e);
3513
- logger.error(`Failed: ${filePath} \u2014 ${msg}`);
3514
- 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 });
3515
3774
  }
3516
3775
  }
3517
3776
  formatResults(results, opts.format, opts.output);
3777
+ cleanup();
3518
3778
  logger.debug(
3519
3779
  `Done: ${results.filter((r) => r.status === "success").length}/${results.length} succeeded`
3520
3780
  );
@@ -3525,21 +3785,19 @@ function registerAnalyzeCommand(parent) {
3525
3785
  }
3526
3786
 
3527
3787
  // src/providers/gemini/commands/extract.ts
3528
- init_errors();
3529
- import fs21 from "fs";
3530
3788
  function registerExtractCommand(parent) {
3531
- 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(
3532
3790
  async (opts) => {
3533
3791
  const logger = createLogger({ verbose: opts.verbose ?? false });
3534
3792
  const model = opts.model ?? getDefaultModel("extract");
3535
- for (const f of opts.files) {
3536
- if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3537
- }
3793
+ const { inputs, cleanup } = await resolveFileInputs(opts.files, {
3794
+ stdinExt: opts.stdinExt
3795
+ });
3538
3796
  const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
3539
3797
  const results = [];
3540
- for (let i = 0; i < opts.files.length; i++) {
3541
- const filePath = opts.files[i];
3542
- 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}`);
3543
3801
  try {
3544
3802
  const resp = await withGeminiKey(
3545
3803
  async (apiKey) => {
@@ -3565,14 +3823,18 @@ function registerExtractCommand(parent) {
3565
3823
  },
3566
3824
  { logger }
3567
3825
  );
3568
- results.push({ file: filePath, status: "success", response: extractText(resp) });
3826
+ results.push({ file: label, status: "success", response: extractText(resp) });
3569
3827
  } catch (e) {
3570
- const msg = e instanceof Error ? e.message : String(e);
3571
- logger.error(`Failed: ${filePath} \u2014 ${msg}`);
3572
- 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 });
3573
3834
  }
3574
3835
  }
3575
3836
  formatResults(results, opts.format, opts.output);
3837
+ cleanup();
3576
3838
  const failed = results.filter((r) => r.status === "error").length;
3577
3839
  if (failed > 0) process.exit(1);
3578
3840
  }
@@ -3583,8 +3845,8 @@ function registerExtractCommand(parent) {
3583
3845
  init_errors();
3584
3846
 
3585
3847
  // src/providers/gemini/generators/speech.ts
3586
- import fs22 from "fs";
3587
- import path24 from "path";
3848
+ import fs21 from "fs";
3849
+ import path25 from "path";
3588
3850
 
3589
3851
  // src/providers/gemini/voices.ts
3590
3852
  var GEMINI_TTS_VOICES = [
@@ -3750,12 +4012,12 @@ function extractWavData(wav) {
3750
4012
  }
3751
4013
  function saveSpeech(outBytes, outputFormat, output, logger) {
3752
4014
  const outDir = getOutputDir();
3753
- const dest = path24.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
3754
- fs22.writeFileSync(dest, outBytes);
4015
+ const dest = path25.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
4016
+ fs21.writeFileSync(dest, outBytes);
3755
4017
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
3756
4018
  if (output) {
3757
- fs22.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3758
- fs22.copyFileSync(dest, output);
4019
+ fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
4020
+ fs21.copyFileSync(dest, output);
3759
4021
  logger?.success(`Copied to: ${output}`);
3760
4022
  }
3761
4023
  return dest;
@@ -3868,11 +4130,11 @@ function collectSpeakers(value, prev) {
3868
4130
 
3869
4131
  // src/providers/gemini/commands/generate-video.ts
3870
4132
  init_errors();
3871
- import fs24 from "fs";
3872
- import path25 from "path";
4133
+ import fs23 from "fs";
4134
+ import path26 from "path";
3873
4135
 
3874
4136
  // src/providers/gemini/veo.ts
3875
- import fs23 from "fs";
4137
+ import fs22 from "fs";
3876
4138
 
3877
4139
  // src/core/async-poll.ts
3878
4140
  init_errors();
@@ -3939,13 +4201,13 @@ var BASE2 = `https://${API_HOST}/v1beta`;
3939
4201
  var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
3940
4202
  var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
3941
4203
  function veoImageFromFile(imgPath) {
3942
- if (!fs23.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
4204
+ if (!fs22.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
3943
4205
  const detected = getMimeType(imgPath);
3944
4206
  const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
3945
4207
  if (!VEO_IMAGE_TYPES.has(mimeType)) {
3946
4208
  throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
3947
4209
  }
3948
- return { bytesBase64Encoded: fs23.readFileSync(imgPath).toString("base64"), mimeType };
4210
+ return { bytesBase64Encoded: fs22.readFileSync(imgPath).toString("base64"), mimeType };
3949
4211
  }
3950
4212
  function veoFramesFromPaths(paths = []) {
3951
4213
  if (paths.length > 2) {
@@ -4070,7 +4332,7 @@ function registerGenerateVideoCommand(parent) {
4070
4332
  logger.debug(`Submitting video generation with ${model}...`);
4071
4333
  logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
4072
4334
  const frames = veoFramesFromPaths(opts.referenceImages);
4073
- const dest = path25.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
4335
+ const dest = path26.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
4074
4336
  const finalOp = await runVeo(
4075
4337
  model,
4076
4338
  {
@@ -4084,8 +4346,8 @@ function registerGenerateVideoCommand(parent) {
4084
4346
  );
4085
4347
  logger.success(`Saved video: ${dest}`);
4086
4348
  if (opts.output) {
4087
- fs24.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
4088
- fs24.copyFileSync(dest, opts.output);
4349
+ fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
4350
+ fs23.copyFileSync(dest, opts.output);
4089
4351
  logger.success(`Copied to: ${opts.output}`);
4090
4352
  }
4091
4353
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4101,8 +4363,8 @@ Generated video: ${dest}`);
4101
4363
 
4102
4364
  // src/providers/gemini/commands/generate.ts
4103
4365
  init_errors();
4104
- import fs25 from "fs";
4105
- import path26 from "path";
4366
+ import fs24 from "fs";
4367
+ import path27 from "path";
4106
4368
  function registerGenerateCommand(parent) {
4107
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(
4108
4370
  async (opts) => {
@@ -4147,8 +4409,8 @@ function registerGenerateCommand(parent) {
4147
4409
  const outDir = getOutputDir();
4148
4410
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
4149
4411
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
4150
- const dest = path26.join(outDir, fname);
4151
- fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
4412
+ const dest = path27.join(outDir, fname);
4413
+ fs24.writeFileSync(dest, Buffer.from(img.data, "base64"));
4152
4414
  savedFiles.push(dest);
4153
4415
  logger.success(`Saved: ${dest}`);
4154
4416
  }
@@ -4163,8 +4425,8 @@ function registerGenerateCommand(parent) {
4163
4425
  process.exit(1);
4164
4426
  }
4165
4427
  if (opts.output && savedFiles[0]) {
4166
- fs25.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
4167
- fs25.copyFileSync(savedFiles[0], opts.output);
4428
+ fs24.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
4429
+ fs24.copyFileSync(savedFiles[0], opts.output);
4168
4430
  logger.success(`Copied to: ${opts.output}`);
4169
4431
  }
4170
4432
  const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
@@ -4176,8 +4438,8 @@ Generated ${finalImages.length} image(s):`);
4176
4438
  }
4177
4439
 
4178
4440
  // src/providers/gemini/commands/image-to-image.ts
4179
- import fs26 from "fs";
4180
- import path27 from "path";
4441
+ import fs25 from "fs";
4442
+ import path28 from "path";
4181
4443
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
4182
4444
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
4183
4445
  function registerGeminiImageToImageCommand(parent) {
@@ -4238,14 +4500,14 @@ function registerGeminiImageToImageCommand(parent) {
4238
4500
  const img = images[i];
4239
4501
  if (!img) continue;
4240
4502
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
4241
- const dest = path27.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
4242
- 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"));
4243
4505
  saved.push(dest);
4244
4506
  logger.success(`Saved: ${dest}`);
4245
4507
  }
4246
4508
  if (opts.output && saved[0]) {
4247
- fs26.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
4248
- fs26.copyFileSync(saved[0], opts.output);
4509
+ fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4510
+ fs25.copyFileSync(saved[0], opts.output);
4249
4511
  logger.success(`Copied to: ${opts.output}`);
4250
4512
  }
4251
4513
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4273,7 +4535,7 @@ function sniffImageMime(buf, url) {
4273
4535
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
4274
4536
  return "image/webp";
4275
4537
  }
4276
- const ext = path27.extname(new URL(url).pathname).toLowerCase();
4538
+ const ext = path28.extname(new URL(url).pathname).toLowerCase();
4277
4539
  if (ext === ".png") return "image/png";
4278
4540
  if (ext === ".webp") return "image/webp";
4279
4541
  if (ext === ".gif") return "image/gif";
@@ -4282,8 +4544,8 @@ function sniffImageMime(buf, url) {
4282
4544
 
4283
4545
  // src/providers/gemini/commands/image-to-video.ts
4284
4546
  init_errors();
4285
- import fs27 from "fs";
4286
- import path28 from "path";
4547
+ import fs26 from "fs";
4548
+ import path29 from "path";
4287
4549
  function registerGeminiImageToVideoCommand(parent) {
4288
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(
4289
4551
  async (imagePath, opts) => {
@@ -4303,7 +4565,7 @@ function registerGeminiImageToVideoCommand(parent) {
4303
4565
  logger.info(
4304
4566
  "Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
4305
4567
  );
4306
- const dest = path28.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
4568
+ const dest = path29.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
4307
4569
  const finalOp = await runVeo(
4308
4570
  model,
4309
4571
  {
@@ -4318,8 +4580,8 @@ function registerGeminiImageToVideoCommand(parent) {
4318
4580
  );
4319
4581
  logger.success(`Saved video: ${dest}`);
4320
4582
  if (opts.output) {
4321
- fs27.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4322
- fs27.copyFileSync(dest, opts.output);
4583
+ fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
4584
+ fs26.copyFileSync(dest, opts.output);
4323
4585
  logger.success(`Copied to: ${opts.output}`);
4324
4586
  }
4325
4587
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4334,20 +4596,21 @@ Generated video: ${dest}`);
4334
4596
  }
4335
4597
 
4336
4598
  // src/providers/gemini/commands/transcribe.ts
4337
- init_errors();
4338
- import fs28 from "fs";
4339
4599
  function registerTranscribeCommand2(parent) {
4340
- 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(
4341
4604
  async (opts) => {
4342
4605
  const logger = createLogger({ verbose: opts.verbose ?? false });
4343
4606
  const model = opts.model ?? getDefaultModel("transcribe");
4344
- for (const f of opts.files) {
4345
- if (!fs28.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
4346
- }
4607
+ const { inputs, cleanup } = await resolveFileInputs(opts.files, {
4608
+ stdinExt: opts.stdinExt
4609
+ });
4347
4610
  const results = [];
4348
- for (let i = 0; i < opts.files.length; i++) {
4349
- const filePath = opts.files[i];
4350
- 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}`);
4351
4614
  try {
4352
4615
  const resp = await withGeminiKey(
4353
4616
  async (apiKey) => {
@@ -4374,17 +4637,21 @@ function registerTranscribeCommand2(parent) {
4374
4637
  { logger }
4375
4638
  );
4376
4639
  results.push({
4377
- file: filePath,
4640
+ file: label,
4378
4641
  status: "success",
4379
4642
  response: extractText(resp)
4380
4643
  });
4381
4644
  } catch (e) {
4382
- const msg = e instanceof Error ? e.message : String(e);
4383
- logger.error(`Failed: ${filePath} \u2014 ${msg}`);
4384
- 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 });
4385
4651
  }
4386
4652
  }
4387
4653
  formatResults(results, opts.format, opts.output);
4654
+ cleanup();
4388
4655
  const failed = results.filter((r) => r.status === "error").length;
4389
4656
  if (failed > 0) process.exit(1);
4390
4657
  }
@@ -4407,8 +4674,8 @@ function registerGeminiCommands(program2) {
4407
4674
  }
4408
4675
 
4409
4676
  // src/providers/leonardo/commands/generate.ts
4410
- import fs29 from "fs";
4411
- import path29 from "path";
4677
+ import fs27 from "fs";
4678
+ import path30 from "path";
4412
4679
 
4413
4680
  // src/providers/leonardo/client.ts
4414
4681
  init_errors();
@@ -4481,11 +4748,11 @@ var LeonardoClient = class {
4481
4748
  }
4482
4749
  apiKey;
4483
4750
  baseUrl;
4484
- async request(path49, opts = {}) {
4751
+ async request(path50, opts = {}) {
4485
4752
  const base = this.baseUrl.replace(/\/$/, "");
4486
- const v2Override = path49.startsWith("v2:");
4753
+ const v2Override = path50.startsWith("v2:");
4487
4754
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
4488
- const finalPath = v2Override ? path49.slice(3) : path49;
4755
+ const finalPath = v2Override ? path50.slice(3) : path50;
4489
4756
  const url = new URL(finalBase + finalPath);
4490
4757
  if (opts.query) {
4491
4758
  for (const [k, v] of Object.entries(opts.query)) {
@@ -4504,14 +4771,14 @@ var LeonardoClient = class {
4504
4771
  timeoutMs: opts.timeoutMs ?? 12e4
4505
4772
  });
4506
4773
  }
4507
- get(path49, query, logger) {
4508
- return this.request(path49, { method: "GET", query, logger });
4774
+ get(path50, query, logger) {
4775
+ return this.request(path50, { method: "GET", query, logger });
4509
4776
  }
4510
- post(path49, body, logger) {
4511
- return this.request(path49, { method: "POST", body, logger });
4777
+ post(path50, body, logger) {
4778
+ return this.request(path50, { method: "POST", body, logger });
4512
4779
  }
4513
- delete(path49, logger) {
4514
- return this.request(path49, { method: "DELETE", logger });
4780
+ delete(path50, logger) {
4781
+ return this.request(path50, { method: "DELETE", logger });
4515
4782
  }
4516
4783
  };
4517
4784
  function createLeonardoClient() {
@@ -4522,7 +4789,7 @@ function createLeonardoClient() {
4522
4789
  function extFromUrl2(url, fallback = ".png") {
4523
4790
  try {
4524
4791
  const u = new URL(url);
4525
- const e = path29.extname(u.pathname);
4792
+ const e = path30.extname(u.pathname);
4526
4793
  return e.length > 0 ? e : fallback;
4527
4794
  } catch {
4528
4795
  return fallback;
@@ -4607,7 +4874,7 @@ function registerLeonardoGenerateCommand(parent) {
4607
4874
  if (!img) continue;
4608
4875
  const ext = extFromUrl2(img.url);
4609
4876
  const filename = autoFilename(generationId, i + 1, ext);
4610
- const filepath = path29.join(outDir, filename);
4877
+ const filepath = path30.join(outDir, filename);
4611
4878
  try {
4612
4879
  await downloadFile(img.url, filepath);
4613
4880
  saved.push(filepath);
@@ -4617,9 +4884,9 @@ function registerLeonardoGenerateCommand(parent) {
4617
4884
  }
4618
4885
  }
4619
4886
  if (opts.output && saved[0]) {
4620
- const dest = path29.resolve(opts.output);
4621
- fs29.mkdirSync(path29.dirname(dest), { recursive: true });
4622
- 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);
4623
4890
  logger.success(`Copied to ${dest}`);
4624
4891
  }
4625
4892
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4631,11 +4898,11 @@ Generated ${finalImages.length} image(s):`);
4631
4898
  }
4632
4899
 
4633
4900
  // src/providers/leonardo/commands/image-to-image.ts
4634
- import fs30 from "fs";
4635
- import path30 from "path";
4901
+ import fs28 from "fs";
4902
+ import path31 from "path";
4636
4903
  function extFromUrl3(url, fallback = ".png") {
4637
4904
  try {
4638
- return path30.extname(new URL(url).pathname) || fallback;
4905
+ return path31.extname(new URL(url).pathname) || fallback;
4639
4906
  } catch {
4640
4907
  return fallback;
4641
4908
  }
@@ -4697,7 +4964,7 @@ function registerLeonardoImageToImageCommand(parent) {
4697
4964
  if (!img) continue;
4698
4965
  const ext = extFromUrl3(img.url);
4699
4966
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
4700
- const filepath = path30.join(outDir, filename);
4967
+ const filepath = path31.join(outDir, filename);
4701
4968
  try {
4702
4969
  await downloadFile(img.url, filepath);
4703
4970
  saved.push(filepath);
@@ -4707,9 +4974,9 @@ function registerLeonardoImageToImageCommand(parent) {
4707
4974
  }
4708
4975
  }
4709
4976
  if (opts.output && saved[0]) {
4710
- const dest = path30.resolve(opts.output);
4711
- fs30.mkdirSync(path30.dirname(dest), { recursive: true });
4712
- 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);
4713
4980
  logger.success(`Copied to ${dest}`);
4714
4981
  }
4715
4982
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4721,7 +4988,7 @@ Generated ${finalImages.length} image(s):`);
4721
4988
  }
4722
4989
 
4723
4990
  // src/providers/leonardo/commands/video.ts
4724
- import path31 from "path";
4991
+ import path32 from "path";
4725
4992
  function v2DimsFor(resolution) {
4726
4993
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
4727
4994
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -4832,7 +5099,7 @@ async function pollAndDownload(client, jobId, opts) {
4832
5099
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
4833
5100
  process.exit(1);
4834
5101
  }
4835
- 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`);
4836
5103
  await downloadFile(videoUrl2, outPath);
4837
5104
  logger.success(`Saved ${outPath}`);
4838
5105
  await maybeDownloadThumb(gen, outPath, {
@@ -4938,7 +5205,7 @@ function registerLeonardoModelsCommand(parent) {
4938
5205
  }
4939
5206
 
4940
5207
  // src/providers/leonardo/commands/status.ts
4941
- import path32 from "path";
5208
+ import path33 from "path";
4942
5209
  var VIDEO_KEYS = ["motionMP4URL", "url"];
4943
5210
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
4944
5211
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -5003,7 +5270,7 @@ function registerLeonardoStatusCommand(parent) {
5003
5270
  const outDir = getOutputDir();
5004
5271
  let idx = 0;
5005
5272
  for (const url of videos) {
5006
- 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`);
5007
5274
  await downloadFile(url, dest);
5008
5275
  logger.success(`Saved ${dest}`);
5009
5276
  await maybeDownloadThumb(gen, dest, {
@@ -5016,7 +5283,7 @@ function registerLeonardoStatusCommand(parent) {
5016
5283
  for (const url of images) {
5017
5284
  const m = url.match(IMAGE_EXT_RE2);
5018
5285
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
5019
- 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}`);
5020
5287
  await downloadFile(url, dest);
5021
5288
  logger.success(`Saved ${dest}`);
5022
5289
  idx++;
@@ -5173,8 +5440,8 @@ function sleep3(ms) {
5173
5440
  }
5174
5441
 
5175
5442
  // src/providers/minimax/generators/music.ts
5176
- import fs31 from "fs";
5177
- import path33 from "path";
5443
+ import fs29 from "fs";
5444
+ import path34 from "path";
5178
5445
  async function generateMinimaxMusic(opts) {
5179
5446
  const {
5180
5447
  apiKey,
@@ -5219,14 +5486,14 @@ async function generateMinimaxMusic(opts) {
5219
5486
  }
5220
5487
  const durationMs = resp.extra_info?.music_duration ?? 0;
5221
5488
  const outDir = getOutputDir();
5222
- const dest = path33.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
5489
+ const dest = path34.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
5223
5490
  try {
5224
5491
  if (audioData.startsWith("http")) {
5225
5492
  const bytes = await fetchBytes(audioData);
5226
- fs31.writeFileSync(dest, bytes);
5493
+ fs29.writeFileSync(dest, bytes);
5227
5494
  } else {
5228
5495
  const audioBytes = Buffer.from(audioData, "hex");
5229
- fs31.writeFileSync(dest, audioBytes);
5496
+ fs29.writeFileSync(dest, audioBytes);
5230
5497
  }
5231
5498
  } catch (e) {
5232
5499
  return {
@@ -5237,8 +5504,8 @@ async function generateMinimaxMusic(opts) {
5237
5504
  const durSec = durationMs / 1e3;
5238
5505
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
5239
5506
  if (output) {
5240
- fs31.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
5241
- fs31.copyFileSync(dest, output);
5507
+ fs29.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5508
+ fs29.copyFileSync(dest, output);
5242
5509
  }
5243
5510
  return { status: "success", generatedAudio: dest, durationMs, model };
5244
5511
  }
@@ -5312,8 +5579,8 @@ Generated music: ${result.generatedAudio}`);
5312
5579
  init_errors();
5313
5580
 
5314
5581
  // src/providers/minimax/generators/speech.ts
5315
- import fs32 from "fs";
5316
- import path34 from "path";
5582
+ import fs30 from "fs";
5583
+ import path35 from "path";
5317
5584
  async function generateMinimaxSpeech(opts) {
5318
5585
  const {
5319
5586
  apiKey,
@@ -5360,12 +5627,12 @@ async function generateMinimaxSpeech(opts) {
5360
5627
  const audioBytes = Buffer.from(audioHex, "hex");
5361
5628
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
5362
5629
  const outDir = getOutputDir();
5363
- const dest = path34.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
5364
- fs32.writeFileSync(dest, audioBytes);
5630
+ const dest = path35.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
5631
+ fs30.writeFileSync(dest, audioBytes);
5365
5632
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
5366
5633
  if (output) {
5367
- fs32.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5368
- fs32.copyFileSync(dest, output);
5634
+ fs30.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5635
+ fs30.copyFileSync(dest, output);
5369
5636
  }
5370
5637
  return { status: "success", generatedAudio: dest, model };
5371
5638
  }
@@ -5412,8 +5679,8 @@ Generated audio: ${result.generatedAudio}`);
5412
5679
  init_errors();
5413
5680
 
5414
5681
  // src/providers/minimax/generators/video.ts
5415
- import fs33 from "fs";
5416
- import path35 from "path";
5682
+ import fs31 from "fs";
5683
+ import path36 from "path";
5417
5684
  async function generateMinimaxVideo(opts) {
5418
5685
  const {
5419
5686
  apiKey,
@@ -5459,18 +5726,18 @@ async function generateMinimaxVideo(opts) {
5459
5726
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
5460
5727
  }
5461
5728
  const outDir = getOutputDir();
5462
- const dest = path35.join(outDir, `minimax_video_${Date.now()}.mp4`);
5729
+ const dest = path36.join(outDir, `minimax_video_${Date.now()}.mp4`);
5463
5730
  try {
5464
5731
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
5465
5732
  } catch (e) {
5466
5733
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
5467
5734
  }
5468
5735
  const elapsed = (Date.now() - start) / 1e3;
5469
- const stat = fs33.statSync(dest);
5736
+ const stat = fs31.statSync(dest);
5470
5737
  const fileSizeMb = stat.size / (1024 * 1024);
5471
5738
  if (output) {
5472
- fs33.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5473
- fs33.copyFileSync(dest, output);
5739
+ fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5740
+ fs31.copyFileSync(dest, output);
5474
5741
  }
5475
5742
  await maybeDownloadThumb(pollResult, dest, {
5476
5743
  skip: thumb === false,
@@ -5532,8 +5799,8 @@ Generated video: ${result.generatedVideo}`);
5532
5799
 
5533
5800
  // src/providers/minimax/generators/image.ts
5534
5801
  init_errors();
5535
- import fs34 from "fs";
5536
- import path36 from "path";
5802
+ import fs32 from "fs";
5803
+ import path37 from "path";
5537
5804
  async function generateMinimaxImage(opts) {
5538
5805
  const {
5539
5806
  apiKey,
@@ -5571,8 +5838,8 @@ async function generateMinimaxImage(opts) {
5571
5838
  try {
5572
5839
  const bytes = await fetchBytes(url);
5573
5840
  const fname = `minimax_image_${Date.now()}_${i}.png`;
5574
- const dest = path36.join(outDir, fname);
5575
- fs34.writeFileSync(dest, bytes);
5841
+ const dest = path37.join(outDir, fname);
5842
+ fs32.writeFileSync(dest, bytes);
5576
5843
  savedFiles.push(dest);
5577
5844
  logger?.success(`Saved: ${dest}`);
5578
5845
  } catch (e) {
@@ -5583,8 +5850,8 @@ async function generateMinimaxImage(opts) {
5583
5850
  }
5584
5851
  }
5585
5852
  if (output && savedFiles[0]) {
5586
- fs34.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5587
- fs34.copyFileSync(savedFiles[0], output);
5853
+ fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
5854
+ fs32.copyFileSync(savedFiles[0], output);
5588
5855
  }
5589
5856
  return { status: "success", generatedImages: savedFiles, model };
5590
5857
  }
@@ -5623,8 +5890,8 @@ Generated ${finalImages.length} image(s):`);
5623
5890
 
5624
5891
  // src/providers/minimax/commands/image-to-image.ts
5625
5892
  init_errors();
5626
- import fs35 from "fs";
5627
- import path37 from "path";
5893
+ import fs33 from "fs";
5894
+ import path38 from "path";
5628
5895
  function registerMinimaxImageToImageCommand(parent) {
5629
5896
  parent.command("image-to-image").alias("i2i").description(
5630
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."
@@ -5679,8 +5946,8 @@ function registerMinimaxImageToImageCommand(parent) {
5679
5946
  if (!url) continue;
5680
5947
  try {
5681
5948
  const bytes = await fetchBytes(url);
5682
- const dest = path37.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
5683
- fs35.writeFileSync(dest, bytes);
5949
+ const dest = path38.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
5950
+ fs33.writeFileSync(dest, bytes);
5684
5951
  saved.push(dest);
5685
5952
  logger.success(`Saved: ${dest}`);
5686
5953
  } catch (e) {
@@ -5691,8 +5958,8 @@ function registerMinimaxImageToImageCommand(parent) {
5691
5958
  }
5692
5959
  }
5693
5960
  if (opts.output && saved[0]) {
5694
- fs35.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
5695
- fs35.copyFileSync(saved[0], opts.output);
5961
+ fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5962
+ fs33.copyFileSync(saved[0], opts.output);
5696
5963
  logger.success(`Copied to: ${opts.output}`);
5697
5964
  }
5698
5965
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -5826,12 +6093,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
5826
6093
  }
5827
6094
 
5828
6095
  // src/providers/openai/openai-audio.ts
5829
- import fs37 from "fs";
5830
- import path39 from "path";
6096
+ import fs35 from "fs";
6097
+ import path40 from "path";
5831
6098
 
5832
6099
  // src/providers/openai/client.ts
5833
- import fs36 from "fs";
5834
- import path38 from "path";
6100
+ import fs34 from "fs";
6101
+ import path39 from "path";
5835
6102
  init_errors();
5836
6103
  var DEFAULT_TIMEOUT_MS3 = 24e4;
5837
6104
  function requireOpenAIKey() {
@@ -5897,10 +6164,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
5897
6164
  return Buffer.from(await res.arrayBuffer());
5898
6165
  }
5899
6166
  function fileFromPath(filePath) {
5900
- const abs = path38.resolve(filePath);
5901
- if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
5902
- const bytes = fs36.readFileSync(abs);
5903
- 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));
5904
6171
  }
5905
6172
 
5906
6173
  // src/providers/openai/openai-audio.ts
@@ -5939,7 +6206,7 @@ async function resolveAudioReferenceDataUrl(ref) {
5939
6206
  }
5940
6207
  function audioMimeFromReference(ref) {
5941
6208
  const pathname = new URL(ref).pathname;
5942
- const ext = path39.extname(pathname).toLowerCase();
6209
+ const ext = path40.extname(pathname).toLowerCase();
5943
6210
  switch (ext) {
5944
6211
  case ".mp3":
5945
6212
  return "audio/mpeg";
@@ -5958,12 +6225,12 @@ function audioMimeFromReference(ref) {
5958
6225
  async function generateOpenAISpeech(opts) {
5959
6226
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
5960
6227
  const ext = extFromAudioFormat(opts.outputFormat);
5961
- const dest = path39.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
5962
- fs37.writeFileSync(dest, bytes);
6228
+ const dest = path40.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
6229
+ fs35.writeFileSync(dest, bytes);
5963
6230
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
5964
6231
  if (opts.output) {
5965
- fs37.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5966
- fs37.copyFileSync(dest, opts.output);
6232
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
6233
+ fs35.copyFileSync(dest, opts.output);
5967
6234
  }
5968
6235
  return dest;
5969
6236
  }
@@ -5985,9 +6252,9 @@ async function transcribeOpenAIAudio(opts) {
5985
6252
  );
5986
6253
  const text = typeof data.text === "string" ? data.text : void 0;
5987
6254
  const ext = opts.format === "text" ? "txt" : "json";
5988
- const dest = opts.output ? path39.resolve(opts.output) : path39.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
5989
- fs37.mkdirSync(path39.dirname(dest), { recursive: true });
5990
- 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));
5991
6258
  opts.logger?.success(`Saved transcript: ${dest}`);
5992
6259
  return { path: dest, text };
5993
6260
  }
@@ -6014,9 +6281,9 @@ Generated audio: ${dest}`);
6014
6281
  }
6015
6282
 
6016
6283
  // src/providers/openai/codex-image-driver.ts
6017
- import fs38 from "fs";
6018
- import os2 from "os";
6019
- import path40 from "path";
6284
+ import fs36 from "fs";
6285
+ import os3 from "os";
6286
+ import path41 from "path";
6020
6287
  import { execa } from "execa";
6021
6288
  init_errors();
6022
6289
  var defaultRunner = async (cmd, args, opts) => {
@@ -6065,12 +6332,12 @@ function sanitizeCodexEnv() {
6065
6332
  async function runCodexImageGeneration(opts) {
6066
6333
  const runner = opts.runner ?? defaultRunner;
6067
6334
  const ext = extFromImageFormat(opts.outputFormat);
6068
- const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
6069
- fs38.mkdirSync(path40.dirname(dest), { recursive: true });
6070
- const tmp = fs38.mkdtempSync(path40.join(os2.tmpdir(), "multix-codex-image-"));
6071
- const schema = path40.join(tmp, "schema.json");
6072
- const result = path40.join(tmp, "result.json");
6073
- 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(
6074
6341
  schema,
6075
6342
  JSON.stringify({
6076
6343
  type: "object",
@@ -6100,7 +6367,7 @@ async function runCodexImageGeneration(opts) {
6100
6367
  if (res.exitCode !== 0) {
6101
6368
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
6102
6369
  }
6103
- if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
6370
+ if (!fs36.existsSync(dest) || fs36.statSync(dest).size === 0) {
6104
6371
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
6105
6372
  }
6106
6373
  return { path: dest };
@@ -6117,8 +6384,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
6117
6384
 
6118
6385
  // src/providers/openai/openai-image.ts
6119
6386
  init_errors();
6120
- import fs39 from "fs";
6121
- import path41 from "path";
6387
+ import fs37 from "fs";
6388
+ import path42 from "path";
6122
6389
  function buildImageGenerationBody(opts) {
6123
6390
  return {
6124
6391
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -6151,14 +6418,14 @@ function saveOpenAIImages(opts) {
6151
6418
  for (let i = 0; i < items.length; i++) {
6152
6419
  const b64 = items[i]?.b64_json;
6153
6420
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
6154
- const dest = path41.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
6155
- 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"));
6156
6423
  saved.push(dest);
6157
6424
  opts.logger?.success(`Saved: ${dest}`);
6158
6425
  }
6159
6426
  if (opts.output && saved[0]) {
6160
- fs39.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
6161
- fs39.copyFileSync(saved[0], opts.output);
6427
+ fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
6428
+ fs37.copyFileSync(saved[0], opts.output);
6162
6429
  opts.logger?.success(`Copied to: ${opts.output}`);
6163
6430
  }
6164
6431
  return saved;
@@ -6167,7 +6434,7 @@ async function resolveImageRefBytes(ref) {
6167
6434
  if (isHttpUrl(ref)) {
6168
6435
  const bytes2 = await fetchBytes(ref);
6169
6436
  const pathname = new URL(ref).pathname;
6170
- const filename = path41.basename(pathname) || "reference.png";
6437
+ const filename = path42.basename(pathname) || "reference.png";
6171
6438
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
6172
6439
  }
6173
6440
  const file = fileFromPath(ref);
@@ -6212,7 +6479,7 @@ function sniffImageMime2(buf, name) {
6212
6479
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
6213
6480
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
6214
6481
  return "image/webp";
6215
- const ext = path41.extname(name).toLowerCase();
6482
+ const ext = path42.extname(name).toLowerCase();
6216
6483
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
6217
6484
  if (ext === ".webp") return "image/webp";
6218
6485
  return "image/png";
@@ -6356,8 +6623,8 @@ function registerOpenAICommands(program2) {
6356
6623
  }
6357
6624
 
6358
6625
  // src/providers/openrouter/client.ts
6359
- import fs40 from "fs";
6360
- import path42 from "path";
6626
+ import fs38 from "fs";
6627
+ import path43 from "path";
6361
6628
  init_errors();
6362
6629
 
6363
6630
  // src/providers/openrouter/payload.ts
@@ -6521,8 +6788,8 @@ async function generateOpenRouterImage(opts) {
6521
6788
  if (!url) continue;
6522
6789
  try {
6523
6790
  const bytes = await fetchBytes(url);
6524
- const dest = path42.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
6525
- fs40.writeFileSync(dest, bytes);
6791
+ const dest = path43.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
6792
+ fs38.writeFileSync(dest, bytes);
6526
6793
  savedFiles.push(dest);
6527
6794
  logger?.success(`Saved: ${dest}`);
6528
6795
  } catch (e) {
@@ -6533,8 +6800,8 @@ async function generateOpenRouterImage(opts) {
6533
6800
  }
6534
6801
  }
6535
6802
  if (output && savedFiles[0]) {
6536
- fs40.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
6537
- 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]));
6538
6805
  }
6539
6806
  return { status: "success", generatedImages: savedFiles, model: usedModel };
6540
6807
  }
@@ -6575,8 +6842,8 @@ Generated ${finalImages.length} image(s):`);
6575
6842
  }
6576
6843
 
6577
6844
  // src/providers/openrouter/commands/image-to-image.ts
6578
- import fs41 from "fs";
6579
- import path43 from "path";
6845
+ import fs39 from "fs";
6846
+ import path44 from "path";
6580
6847
  init_errors();
6581
6848
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
6582
6849
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
@@ -6668,14 +6935,14 @@ async function runImageToImage(opts) {
6668
6935
  const url = parsed.urls[i];
6669
6936
  if (!url) continue;
6670
6937
  const bytes = await fetchBytes(url);
6671
- const dest = path43.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
6672
- fs41.writeFileSync(dest, bytes);
6938
+ const dest = path44.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
6939
+ fs39.writeFileSync(dest, bytes);
6673
6940
  saved.push(dest);
6674
6941
  opts.logger.success(`Saved: ${dest}`);
6675
6942
  }
6676
6943
  if (opts.output && saved[0]) {
6677
- fs41.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
6678
- fs41.copyFileSync(saved[0], opts.output);
6944
+ fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6945
+ fs39.copyFileSync(saved[0], opts.output);
6679
6946
  opts.logger.success(`Copied to: ${opts.output}`);
6680
6947
  }
6681
6948
  return saved;
@@ -6720,13 +6987,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
6720
6987
  }
6721
6988
 
6722
6989
  // src/providers/openrouter/commands/video-status.ts
6723
- import fs42 from "fs";
6724
- import path44 from "path";
6990
+ import fs40 from "fs";
6991
+ import path45 from "path";
6725
6992
  async function downloadOpenRouterVideo(opts) {
6726
6993
  const { jobId, logger, source } = opts;
6727
6994
  const apiKey = requireOpenRouterKey();
6728
6995
  const url = videoContentUrl(jobId);
6729
- const dest = path44.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
6996
+ const dest = path45.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
6730
6997
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
6731
6998
  if (!r.ok) {
6732
6999
  logger.error(`Download HTTP ${r.status}`);
@@ -6736,14 +7003,14 @@ async function downloadOpenRouterVideo(opts) {
6736
7003
  logger.error("Empty response body");
6737
7004
  process.exit(1);
6738
7005
  }
6739
- fs42.mkdirSync(path44.dirname(dest), { recursive: true });
6740
- const ws = fs42.createWriteStream(dest);
7006
+ fs40.mkdirSync(path45.dirname(dest), { recursive: true });
7007
+ const ws = fs40.createWriteStream(dest);
6741
7008
  const { pipeline: pipeline2 } = await import("stream/promises");
6742
7009
  await pipeline2(r.body, ws);
6743
7010
  logger.success(`Saved ${dest}`);
6744
7011
  if (opts.output) {
6745
- fs42.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6746
- fs42.copyFileSync(dest, opts.output);
7012
+ fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
7013
+ fs40.copyFileSync(dest, opts.output);
6747
7014
  logger.success(`Copied to ${opts.output}`);
6748
7015
  }
6749
7016
  await maybeDownloadThumb(source, dest, {
@@ -6908,6 +7175,10 @@ function registerOpenRouterCommands(program2) {
6908
7175
  registerOpenRouterVideoModelsCommand(openrouter);
6909
7176
  }
6910
7177
 
7178
+ // src/commands/check.ts
7179
+ import fs41 from "fs";
7180
+ import os4 from "os";
7181
+
6911
7182
  // src/commands/check-helpers/binary-check.ts
6912
7183
  import { execa as execa2 } from "execa";
6913
7184
  async function checkBinary(binary, versionFlag = "--version") {
@@ -7079,6 +7350,19 @@ function registerCheckCommand(program2) {
7079
7350
  if (opts.verbose) console.log(` ${MAGICK_HINT}`);
7080
7351
  }
7081
7352
  logger.header("API Keys");
7353
+ if (opts.verbose) {
7354
+ const files = getLoadedEnvFiles();
7355
+ logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
7356
+ }
7357
+ if (!legacyClaudeEnvEnabled()) {
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));
7360
+ if (legacy.length > 0) {
7361
+ logger.info(
7362
+ `Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
7363
+ );
7364
+ }
7365
+ }
7082
7366
  let anyKey = false;
7083
7367
  const resolved = /* @__PURE__ */ new Map();
7084
7368
  for (const p of PROVIDERS) {
@@ -7177,8 +7461,8 @@ function registerCheckCommand(program2) {
7177
7461
 
7178
7462
  // src/commands/doc/convert.ts
7179
7463
  init_errors();
7180
- import fs43 from "fs";
7181
- import path45 from "path";
7464
+ import fs42 from "fs";
7465
+ import path46 from "path";
7182
7466
 
7183
7467
  // src/commands/doc/default-prompt.ts
7184
7468
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -7199,18 +7483,18 @@ async function convertDocuments(opts) {
7199
7483
  const model = opts.model ?? DOC_MODEL_DEFAULT;
7200
7484
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
7201
7485
  for (const f of inputs) {
7202
- if (!fs43.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
7486
+ if (!fs42.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
7203
7487
  }
7204
7488
  let outputPath;
7205
7489
  if (output) {
7206
- outputPath = path45.resolve(output);
7490
+ outputPath = path46.resolve(output);
7207
7491
  } else if (autoName && inputs.length === 1 && inputs[0]) {
7208
- const base = path45.basename(inputs[0], path45.extname(inputs[0]));
7209
- 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`);
7210
7494
  } else {
7211
- outputPath = path45.join(getOutputDir(), "document-extraction.md");
7495
+ outputPath = path46.join(getOutputDir(), "document-extraction.md");
7212
7496
  }
7213
- fs43.mkdirSync(path45.dirname(outputPath), { recursive: true });
7497
+ fs42.mkdirSync(path46.dirname(outputPath), { recursive: true });
7214
7498
  const results = [];
7215
7499
  for (let i = 0; i < inputs.length; i++) {
7216
7500
  const filePath = inputs[i];
@@ -7270,7 +7554,7 @@ async function convertDocuments(opts) {
7270
7554
  `);
7271
7555
  for (const r of results) {
7272
7556
  lines.push(`
7273
- ## ${path45.basename(r.file)}
7557
+ ## ${path46.basename(r.file)}
7274
7558
  `);
7275
7559
  if (r.status === "success" && r.markdown) {
7276
7560
  lines.push(`${r.markdown}
@@ -7283,7 +7567,7 @@ async function convertDocuments(opts) {
7283
7567
  }
7284
7568
  lines.push("\n---\n");
7285
7569
  }
7286
- fs43.writeFileSync(outputPath, lines.join("\n"), "utf8");
7570
+ fs42.writeFileSync(outputPath, lines.join("\n"), "utf8");
7287
7571
  const succeeded = results.filter((r) => r.status === "success").length;
7288
7572
  console.log(`
7289
7573
  ${"=".repeat(50)}`);
@@ -7331,12 +7615,12 @@ function registerDocCommands(program2) {
7331
7615
 
7332
7616
  // src/commands/media/index.ts
7333
7617
  init_errors();
7334
- import fs46 from "fs";
7335
- import path48 from "path";
7618
+ import fs45 from "fs";
7619
+ import path49 from "path";
7336
7620
 
7337
7621
  // src/commands/media/batch.ts
7338
- import fs44 from "fs";
7339
- import path46 from "path";
7622
+ import fs43 from "fs";
7623
+ import path47 from "path";
7340
7624
 
7341
7625
  // src/commands/media/detect-type.ts
7342
7626
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -7532,10 +7816,10 @@ async function batchOptimize(opts) {
7532
7816
  verbose,
7533
7817
  logger
7534
7818
  } = opts;
7535
- fs44.mkdirSync(outputDir, { recursive: true });
7536
- const entries = fs44.readdirSync(inputDir);
7819
+ fs43.mkdirSync(outputDir, { recursive: true });
7820
+ const entries = fs43.readdirSync(inputDir);
7537
7821
  const mediaFiles = entries.filter((f) => {
7538
- const ext = path46.extname(f).toLowerCase();
7822
+ const ext = path47.extname(f).toLowerCase();
7539
7823
  return ALL_EXTS.has(ext);
7540
7824
  });
7541
7825
  if (mediaFiles.length === 0) {
@@ -7546,9 +7830,9 @@ async function batchOptimize(opts) {
7546
7830
  let succeeded = 0;
7547
7831
  const errors = [];
7548
7832
  for (const file of mediaFiles) {
7549
- const inputPath = path46.join(inputDir, file);
7550
- const outputPath = path46.join(outputDir, file);
7551
- 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();
7552
7836
  const kind = extToKind(ext);
7553
7837
  logger?.debug(`Processing: ${file}`);
7554
7838
  try {
@@ -7585,8 +7869,8 @@ async function batchOptimize(opts) {
7585
7869
 
7586
7870
  // src/commands/media/split-video.ts
7587
7871
  init_errors();
7588
- import fs45 from "fs";
7589
- import path47 from "path";
7872
+ import fs44 from "fs";
7873
+ import path48 from "path";
7590
7874
  async function splitVideo(opts) {
7591
7875
  await assertFfmpeg();
7592
7876
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -7597,10 +7881,10 @@ async function splitVideo(opts) {
7597
7881
  logger?.info("Video is short enough \u2014 no splitting needed");
7598
7882
  return [input];
7599
7883
  }
7600
- fs45.mkdirSync(outputDir, { recursive: true });
7601
- const ext = path47.extname(input) || ".mp4";
7602
- const baseName = path47.basename(input, ext);
7603
- 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}`);
7604
7888
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
7605
7889
  const args = [
7606
7890
  "-i",
@@ -7619,8 +7903,8 @@ async function splitVideo(opts) {
7619
7903
  await runFfmpeg(args, { verbose, logger });
7620
7904
  const files = [];
7621
7905
  for (let i = 0; i < numChunks + 1; i++) {
7622
- const fname = path47.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
7623
- if (fs45.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);
7624
7908
  }
7625
7909
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
7626
7910
  return files;
@@ -7634,9 +7918,9 @@ function registerMediaCommands(program2) {
7634
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(
7635
7919
  async (opts) => {
7636
7920
  const logger = createLogger({ verbose: opts.verbose ?? false });
7637
- if (!fs46.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7638
- fs46.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
7639
- 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();
7640
7924
  const kind = extToKind(ext);
7641
7925
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
7642
7926
  const quality = Number.parseInt(opts.quality, 10);
@@ -7676,7 +7960,7 @@ function registerMediaCommands(program2) {
7676
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(
7677
7961
  async (opts) => {
7678
7962
  const logger = createLogger({ verbose: opts.verbose ?? false });
7679
- if (!fs46.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}`);
7680
7964
  const chunks = await splitVideo({
7681
7965
  input: opts.input,
7682
7966
  outputDir: opts.outputDir,
@@ -7692,7 +7976,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
7692
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(
7693
7977
  async (opts) => {
7694
7978
  const logger = createLogger({ verbose: opts.verbose ?? false });
7695
- if (!fs46.existsSync(opts.inputDir))
7979
+ if (!fs45.existsSync(opts.inputDir))
7696
7980
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
7697
7981
  const result = await batchOptimize({
7698
7982
  inputDir: opts.inputDir,