@mrgoonie/multix 0.1.0-beta → 0.1.0-beta.2

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
@@ -110,11 +110,14 @@ async function httpJson(opts) {
110
110
  }
111
111
  return response.json();
112
112
  }
113
- async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
113
+ async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS, headers = {}) {
114
114
  fs4.mkdirSync(path4.dirname(dest), { recursive: true });
115
115
  let response;
116
116
  try {
117
- response = await globalThis.fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
117
+ response = await globalThis.fetch(url, {
118
+ headers,
119
+ signal: AbortSignal.timeout(timeoutMs)
120
+ });
118
121
  } catch (cause) {
119
122
  const msg = cause instanceof Error ? cause.message : String(cause);
120
123
  throw new HttpError(0, msg, url);
@@ -414,9 +417,9 @@ var BytePlusClient = class {
414
417
  }
415
418
  apiKey;
416
419
  baseUrl;
417
- async request(path45, opts = {}) {
420
+ async request(path46, opts = {}) {
418
421
  const base = this.baseUrl.replace(/\/$/, "");
419
- const url = new URL(base + path45);
422
+ const url = new URL(base + path46);
420
423
  if (opts.query) {
421
424
  for (const [k, v] of Object.entries(opts.query)) {
422
425
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -434,14 +437,14 @@ var BytePlusClient = class {
434
437
  timeoutMs: opts.timeoutMs ?? 12e4
435
438
  });
436
439
  }
437
- get(path45, query, logger) {
438
- return this.request(path45, { method: "GET", query, logger });
440
+ get(path46, query, logger) {
441
+ return this.request(path46, { method: "GET", query, logger });
439
442
  }
440
- post(path45, body, logger) {
441
- return this.request(path45, { method: "POST", body, logger });
443
+ post(path46, body, logger) {
444
+ return this.request(path46, { method: "POST", body, logger });
442
445
  }
443
- delete(path45, logger) {
444
- return this.request(path45, { method: "DELETE", logger });
446
+ delete(path46, logger) {
447
+ return this.request(path46, { method: "DELETE", logger });
445
448
  }
446
449
  };
447
450
  function createBytePlusClient() {
@@ -990,10 +993,10 @@ function toRef(kind, resolved, role) {
990
993
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
991
994
  }
992
995
  async function resolveRefSpec(kind, spec, logger) {
993
- const { path: path45, role } = parseRefSpec(spec);
994
- if (kind === "image") return toRef(kind, await resolveImageInput(path45, logger), role);
995
- if (kind === "video") return toRef(kind, await resolveVideoInput(path45, logger), role);
996
- return toRef(kind, await resolveAudioInput(path45, logger), role);
996
+ const { path: path46, role } = parseRefSpec(spec);
997
+ if (kind === "image") return toRef(kind, await resolveImageInput(path46, logger), role);
998
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path46, logger), role);
999
+ return toRef(kind, await resolveAudioInput(path46, logger), role);
997
1000
  }
998
1001
  async function resolveRefSpecs(specs, logger) {
999
1002
  const out = [];
@@ -2076,7 +2079,7 @@ function requiresProcessingWait(mimeType) {
2076
2079
  var BASE = "https://generativelanguage.googleapis.com";
2077
2080
  var FILES_BASE = `${BASE}/upload/v1beta/files`;
2078
2081
  var GENERATE_BASE = `${BASE}/v1beta/models`;
2079
- function requireApiKey() {
2082
+ function requireGeminiApiKey() {
2080
2083
  const key = resolveKey("GEMINI_API_KEY");
2081
2084
  if (!key)
2082
2085
  throw new ConfigError(
@@ -2084,11 +2087,14 @@ function requireApiKey() {
2084
2087
  );
2085
2088
  return key;
2086
2089
  }
2087
- function authHeaders2(apiKey) {
2090
+ function geminiAuthHeaders(apiKey) {
2088
2091
  return { "x-goog-api-key": apiKey };
2089
2092
  }
2093
+ function unwrapGeminiFileRef(response) {
2094
+ return "file" in response ? response.file : response;
2095
+ }
2090
2096
  async function uploadFile(filePath, opts = {}) {
2091
- const apiKey = requireApiKey();
2097
+ const apiKey = requireGeminiApiKey();
2092
2098
  const { timeoutMs = 3e5, logger } = opts;
2093
2099
  const mimeType = getMimeType(filePath);
2094
2100
  const fileBytes = fs12.readFileSync(filePath);
@@ -2137,8 +2143,8 @@ Content-Type: ${mimeType}\r
2137
2143
  await sleep(2e3);
2138
2144
  fileRef = await httpJson({
2139
2145
  url: `${BASE}/v1beta/${fileRef.name}`,
2140
- headers: authHeaders2(apiKey)
2141
- }).then((r) => r.file);
2146
+ headers: geminiAuthHeaders(apiKey)
2147
+ }).then(unwrapGeminiFileRef);
2142
2148
  logger?.debug(`Processing... state=${fileRef.state}`);
2143
2149
  }
2144
2150
  if (fileRef.state === "FAILED")
@@ -2150,11 +2156,11 @@ Content-Type: ${mimeType}\r
2150
2156
  return fileRef;
2151
2157
  }
2152
2158
  async function generateContent(req) {
2153
- const apiKey = requireApiKey();
2159
+ const apiKey = requireGeminiApiKey();
2154
2160
  return httpJson({
2155
2161
  url: `${GENERATE_BASE}/${req.model}:generateContent`,
2156
2162
  method: "POST",
2157
- headers: authHeaders2(apiKey),
2163
+ headers: geminiAuthHeaders(apiKey),
2158
2164
  body: {
2159
2165
  contents: req.contents,
2160
2166
  ...req.generationConfig ? { generationConfig: req.generationConfig } : {}
@@ -2196,10 +2202,12 @@ function sleep(ms) {
2196
2202
  // src/providers/gemini/models.ts
2197
2203
  var IMAGE_MODEL_DEFAULT = "gemini-3.1-flash-image-preview";
2198
2204
  var IMAGE_MODEL_FALLBACK = "gemini-2.5-flash-image";
2205
+ var IMAGE_MODEL_LITE = "gemini-3.1-flash-lite-image";
2199
2206
  var VIDEO_MODEL_DEFAULT = "veo-3.1-generate-preview";
2200
- var ANALYSIS_MODEL_DEFAULT = "gemini-2.5-flash";
2207
+ var TEXT_MODEL_DEFAULT = "gemini-3.6-flash";
2208
+ var ANALYSIS_MODEL_DEFAULT = TEXT_MODEL_DEFAULT;
2201
2209
  var TTS_MODEL_DEFAULT = "gemini-3.1-flash-tts-preview";
2202
- var DOC_MODEL_DEFAULT = "gemini-2.5-flash";
2210
+ var DOC_MODEL_DEFAULT = TEXT_MODEL_DEFAULT;
2203
2211
  var IMAGEN_MODELS = /* @__PURE__ */ new Set([
2204
2212
  "imagen-4.0-generate-001",
2205
2213
  "imagen-4.0-ultra-generate-001",
@@ -2210,6 +2218,7 @@ var GEMINI_IMAGE_MODELS = /* @__PURE__ */ new Set([
2210
2218
  "gemini-3-pro-image-preview",
2211
2219
  "gemini-2.5-flash-image",
2212
2220
  "gemini-2.5-flash-image-preview",
2221
+ IMAGE_MODEL_LITE,
2213
2222
  ...IMAGEN_MODELS
2214
2223
  ]);
2215
2224
  var ASPECT_RATIOS = [
@@ -2225,6 +2234,15 @@ var ASPECT_RATIOS = [
2225
2234
  "21:9"
2226
2235
  ];
2227
2236
  var IMAGE_SIZES = ["1K", "2K", "4K"];
2237
+ function resolveImageOptions(model, aspectRatio, requestedSize) {
2238
+ if (model !== IMAGE_MODEL_LITE) {
2239
+ return { aspectRatio, ...requestedSize ? { imageSize: requestedSize } : {} };
2240
+ }
2241
+ if (requestedSize && requestedSize !== "1K") {
2242
+ throw new Error(`${IMAGE_MODEL_LITE} supports only 1K image output.`);
2243
+ }
2244
+ return { aspectRatio, imageSize: "1K" };
2245
+ }
2228
2246
  function getDefaultModel(task) {
2229
2247
  if (task === "generate") {
2230
2248
  return process.env.IMAGE_GEN_MODEL ?? process.env.GEMINI_IMAGE_GEN_MODEL ?? IMAGE_MODEL_DEFAULT;
@@ -2709,6 +2727,13 @@ function sleep2(ms) {
2709
2727
  init_errors();
2710
2728
  import fs18 from "fs";
2711
2729
  import path22 from "path";
2730
+ function buildImageGenerationConfig(model, aspectRatio, size) {
2731
+ const imageOptions = resolveImageOptions(model, aspectRatio, size);
2732
+ return {
2733
+ responseModalities: ["IMAGE"],
2734
+ imageConfig: imageOptions
2735
+ };
2736
+ }
2712
2737
  function registerGenerateCommand(parent) {
2713
2738
  parent.command("generate").description("Generate images with Gemini (Nano Banana / Imagen 4)").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "Gemini image model to use").option("--aspect-ratio <ratio>", `Aspect ratio (${ASPECT_RATIOS.join("|")})`, "1:1").option("--num-images <n>", "Number of images to generate (1-4)", "1").option("--size <sz>", `Image size (${IMAGE_SIZES.join("|")})`).option("--output <path>", "Copy first generated image to this path").option("-v, --verbose", "Verbose logging").action(
2714
2739
  async (opts) => {
@@ -2725,12 +2750,11 @@ function registerGenerateCommand(parent) {
2725
2750
  }
2726
2751
  logger.debug(`Generating ${numImages} image(s) with model: ${model}`);
2727
2752
  logger.debug(`Aspect ratio: ${opts.aspectRatio}${opts.size ? `, size: ${opts.size}` : ""}`);
2728
- const imageConfig = { aspectRatio: opts.aspectRatio };
2729
- if (opts.size) imageConfig.imageSize = opts.size;
2730
- const generationConfig = {
2731
- responseModalities: ["IMAGE"],
2732
- imageConfig
2733
- };
2753
+ const generationConfig = buildImageGenerationConfig(
2754
+ model,
2755
+ opts.aspectRatio,
2756
+ opts.size
2757
+ );
2734
2758
  const savedFiles = [];
2735
2759
  for (let i = 0; i < numImages; i++) {
2736
2760
  try {
@@ -2782,6 +2806,10 @@ import fs19 from "fs";
2782
2806
  import path23 from "path";
2783
2807
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
2784
2808
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
2809
+ function buildImageToImageGenerationConfig(model, aspectRatio, size) {
2810
+ const imageOptions = resolveImageOptions(model, aspectRatio, size);
2811
+ return imageOptions.imageSize ? { responseModalities: ["IMAGE"], imageConfig: imageOptions } : void 0;
2812
+ }
2785
2813
  function registerGeminiImageToImageCommand(parent) {
2786
2814
  parent.command("image-to-image").alias("i2i").description(
2787
2815
  "Edit/compose images with Gemini 2.5 Flash Image (Nano Banana) using one or more reference images"
@@ -2790,13 +2818,26 @@ function registerGeminiImageToImageCommand(parent) {
2790
2818
  "Reference image (URL or local path); repeatable for multi-ref",
2791
2819
  collect4,
2792
2820
  []
2793
- ).option("-m, --model <id>", `Gemini image model (default ${IMAGE_MODEL_FALLBACK})`).option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
2821
+ ).option("-m, --model <id>", `Gemini image model (default ${IMAGE_MODEL_FALLBACK})`).option("--aspect-ratio <ratio>", `Aspect ratio (${ASPECT_RATIOS.join("|")})`, "1:1").option("--size <sz>", `Image size (${IMAGE_SIZES.join("|")})`).option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
2794
2822
  async (opts) => {
2795
2823
  if (!opts.ref || opts.ref.length === 0) {
2796
2824
  throw new Error("At least one --ref is required for image-to-image.");
2797
2825
  }
2798
2826
  const logger = createLogger({ verbose: opts.verbose ?? false });
2799
2827
  const model = opts.model ?? IMAGE_MODEL_FALLBACK;
2828
+ if (model === IMAGE_MODEL_LITE && opts.ref.length > 1) {
2829
+ logger.warn(
2830
+ `${IMAGE_MODEL_LITE} is not optimized for multiple reference images; use one when possible.`
2831
+ );
2832
+ }
2833
+ if (!ASPECT_RATIOS.includes(opts.aspectRatio)) {
2834
+ throw new Error(
2835
+ `Invalid aspect ratio: ${opts.aspectRatio}. Valid: ${ASPECT_RATIOS.join(", ")}`
2836
+ );
2837
+ }
2838
+ if (opts.size && !IMAGE_SIZES.includes(opts.size)) {
2839
+ throw new Error(`Invalid size: ${opts.size}. Valid: ${IMAGE_SIZES.join(", ")}`);
2840
+ }
2800
2841
  const parts = [];
2801
2842
  for (const r of opts.ref) {
2802
2843
  const resolved = await resolveImageInput(r, {
@@ -2820,10 +2861,15 @@ function registerGeminiImageToImageCommand(parent) {
2820
2861
  }
2821
2862
  parts.push({ text: opts.prompt });
2822
2863
  logger.debug(`Calling ${model} with ${opts.ref.length} reference image(s)`);
2864
+ const generationConfig = buildImageToImageGenerationConfig(
2865
+ model,
2866
+ opts.aspectRatio,
2867
+ opts.size
2868
+ );
2823
2869
  const req = {
2824
2870
  model,
2825
2871
  contents: [{ parts }],
2826
- generationConfig: { responseModalities: ["IMAGE"] }
2872
+ ...generationConfig ? { generationConfig } : {}
2827
2873
  };
2828
2874
  const resp = await generateContent(req);
2829
2875
  const images = extractImages(resp);
@@ -2984,16 +3030,186 @@ Generated video: ${dest}`);
2984
3030
  );
2985
3031
  }
2986
3032
 
2987
- // src/providers/gemini/commands/transcribe.ts
3033
+ // src/providers/gemini/commands/omni-video.ts
2988
3034
  init_errors();
2989
3035
  import fs21 from "fs";
3036
+ import path25 from "path";
3037
+
3038
+ // src/providers/gemini/interactions.ts
3039
+ init_errors();
3040
+ init_http_client();
3041
+ var BASE4 = "https://generativelanguage.googleapis.com/v1beta";
3042
+ var OMNI_MODEL = "gemini-omni-flash-preview";
3043
+ function buildOmniInteractionPayload(request) {
3044
+ return {
3045
+ model: OMNI_MODEL,
3046
+ input: buildOmniInput(request),
3047
+ response_format: { type: "video", delivery: "uri" },
3048
+ store: true,
3049
+ ...request.previousInteractionId ? { previous_interaction_id: request.previousInteractionId } : {}
3050
+ };
3051
+ }
3052
+ async function createOmniInteraction(request) {
3053
+ const apiKey = requireGeminiApiKey();
3054
+ const raw = await httpJson({
3055
+ url: `${BASE4}/interactions`,
3056
+ method: "POST",
3057
+ headers: geminiAuthHeaders(apiKey),
3058
+ body: buildOmniInteractionPayload(request)
3059
+ });
3060
+ if (!raw.id)
3061
+ throw new ProviderError("Omni response did not include an interaction ID.", "gemini");
3062
+ return { interactionId: raw.id, video: extractOmniVideo(raw) };
3063
+ }
3064
+ function buildOmniInput(request) {
3065
+ const parts = [{ type: "text", text: request.prompt }];
3066
+ for (const image of request.images ?? []) {
3067
+ parts.push({ type: "image", data: image.data, mime_type: image.mimeType });
3068
+ }
3069
+ if (request.videoFileUri) parts.push({ type: "document", uri: request.videoFileUri });
3070
+ return parts;
3071
+ }
3072
+ function extractOmniVideo(raw) {
3073
+ for (const step of raw.steps ?? []) {
3074
+ if (step.type !== "model_output") continue;
3075
+ for (const content of step.content ?? []) {
3076
+ if (content.type !== "video") continue;
3077
+ const mimeType = content.mime_type ?? "video/mp4";
3078
+ if (content.data) return { kind: "inline", data: content.data, mimeType };
3079
+ if (content.uri) return { kind: "uri", uri: content.uri, mimeType };
3080
+ }
3081
+ }
3082
+ throw new ProviderError("Omni response did not include a generated video.", "gemini");
3083
+ }
3084
+ async function waitForGeminiFile(fileUri, timeoutMs = 3e5) {
3085
+ const apiKey = requireGeminiApiKey();
3086
+ const name = fileNameFromUri(fileUri);
3087
+ const deadline = Date.now() + timeoutMs;
3088
+ while (Date.now() < deadline) {
3089
+ const response = await httpJson({
3090
+ url: `${BASE4}/${name}`,
3091
+ headers: geminiAuthHeaders(apiKey)
3092
+ });
3093
+ const file = unwrapGeminiFile(response);
3094
+ if (file.state === "ACTIVE") return file;
3095
+ if (file.state === "FAILED") throw new ProviderError("Generated Omni video failed.", "gemini");
3096
+ await sleep4(5e3);
3097
+ }
3098
+ throw new ProviderError("Timed out waiting for generated Omni video.", "gemini");
3099
+ }
3100
+ async function downloadGeminiFile(fileUri, destination) {
3101
+ const apiKey = requireGeminiApiKey();
3102
+ await downloadFile(fileUri, destination, void 0, geminiAuthHeaders(apiKey));
3103
+ }
3104
+ function fileNameFromUri(fileUri) {
3105
+ const pathname = new URL(fileUri).pathname;
3106
+ const match = pathname.match(/\/(files\/[^/:]+)(?::download)?$/);
3107
+ if (!match?.[1]) throw new ProviderError(`Unexpected Gemini file URI: ${fileUri}`, "gemini");
3108
+ return match[1];
3109
+ }
3110
+ function unwrapGeminiFile(response) {
3111
+ return "file" in response ? response.file : response;
3112
+ }
3113
+ function selectGeminiDownloadUri(file, fallbackUri) {
3114
+ return file.downloadUri ?? fallbackUri;
3115
+ }
3116
+ function sleep4(ms) {
3117
+ return new Promise((resolve) => setTimeout(resolve, ms));
3118
+ }
3119
+
3120
+ // src/providers/gemini/commands/omni-video.ts
3121
+ function registerOmniVideoCommand(parent) {
3122
+ parent.command("omni-video").description("Generate or edit video with Gemini Omni Flash [PAID PREVIEW]").requiredOption("--prompt <text>", "Video prompt or concise edit instruction").option("--image <paths...>", "Local image guidance files").option("--video <path>", "One local video to edit (not available in EEA/CH/UK)").option("--previous-interaction-id <id>", "Continue a prior Gemini-stored interaction").option("--upload-timeout <ms>", "Files API processing timeout in ms", "300000").option("--output <path>", "Copy generated video to this path").option("-v, --verbose", "Verbose logging").addHelpText(
3123
+ "after",
3124
+ "\nPreview, paid-tier API. Outputs 3-10 second 720p videos. Audio references, video extension/interpolation, voice editing, and multiple videos are unsupported. Follow-up edits require provider-side stored interactions; no local history is kept."
3125
+ ).action(async (opts) => {
3126
+ const logger = createLogger({ verbose: opts.verbose ?? false });
3127
+ validateInputs(opts);
3128
+ const images = (opts.image ?? []).map(readImage);
3129
+ const videoFile = opts.video ? await uploadFile(opts.video, { timeoutMs: parseTimeout(opts.uploadTimeout), logger }) : void 0;
3130
+ const result = await createOmniInteraction({
3131
+ prompt: opts.prompt,
3132
+ images,
3133
+ videoFileUri: videoFile?.uri,
3134
+ previousInteractionId: opts.previousInteractionId
3135
+ });
3136
+ const destination = path25.join(getOutputDir(), `gemini_omni_${Date.now()}.mp4`);
3137
+ if (result.video.kind === "inline") {
3138
+ fs21.writeFileSync(destination, Buffer.from(result.video.data, "base64"));
3139
+ } else {
3140
+ const generated = await waitForGeminiFile(result.video.uri);
3141
+ await downloadGeminiFile(selectGeminiDownloadUri(generated, result.video.uri), destination);
3142
+ }
3143
+ const metadataPath = `${destination}.json`;
3144
+ fs21.writeFileSync(
3145
+ metadataPath,
3146
+ `${JSON.stringify(buildOmniVideoMetadata(opts, result.interactionId), null, 2)}
3147
+ `
3148
+ );
3149
+ if (opts.output) {
3150
+ fs21.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
3151
+ fs21.copyFileSync(destination, opts.output);
3152
+ fs21.copyFileSync(metadataPath, `${opts.output}.json`);
3153
+ }
3154
+ logger.success(`Saved video: ${destination}`);
3155
+ logger.success(`Saved metadata: ${metadataPath}`);
3156
+ if (opts.output) logger.success(`Copied to: ${opts.output}`);
3157
+ console.log(`Interaction ID: ${result.interactionId}`);
3158
+ });
3159
+ }
3160
+ function buildOmniVideoMetadata(opts, interactionId) {
3161
+ return {
3162
+ provider: "gemini",
3163
+ model: "gemini-omni-flash-preview",
3164
+ prompt: opts.prompt,
3165
+ inputFiles: [...opts.image ?? [], ...opts.video ? [opts.video] : []].map(
3166
+ (filePath) => path25.basename(filePath)
3167
+ ),
3168
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3169
+ interactionId
3170
+ };
3171
+ }
3172
+ function validateInputs(opts) {
3173
+ if (opts.video && opts.image?.length) {
3174
+ throw new ValidationError("Use either --video or --image guidance, not both.");
3175
+ }
3176
+ if (opts.video) {
3177
+ requireLocalFile(opts.video, "video");
3178
+ if (!getMimeType(opts.video).startsWith("video/")) {
3179
+ throw new ValidationError(`Unsupported video file: ${opts.video}`);
3180
+ }
3181
+ }
3182
+ for (const imagePath of opts.image ?? []) {
3183
+ requireLocalFile(imagePath, "image");
3184
+ if (!getMimeType(imagePath).startsWith("image/")) {
3185
+ throw new ValidationError(`Unsupported image file: ${imagePath}`);
3186
+ }
3187
+ }
3188
+ }
3189
+ function requireLocalFile(filePath, label) {
3190
+ if (/^https?:\/\//i.test(filePath)) throw new ValidationError(`${label} URLs are not supported.`);
3191
+ if (!fs21.existsSync(filePath)) throw new ValidationError(`${label} file not found: ${filePath}`);
3192
+ }
3193
+ function readImage(filePath) {
3194
+ return { data: fs21.readFileSync(filePath).toString("base64"), mimeType: getMimeType(filePath) };
3195
+ }
3196
+ function parseTimeout(value) {
3197
+ const timeout = Number.parseInt(value, 10);
3198
+ if (!Number.isFinite(timeout) || timeout <= 0)
3199
+ throw new ValidationError("--upload-timeout must be positive.");
3200
+ return timeout;
3201
+ }
3202
+
3203
+ // src/providers/gemini/commands/transcribe.ts
3204
+ init_errors();
3205
+ import fs22 from "fs";
2990
3206
  function registerTranscribeCommand2(parent) {
2991
3207
  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(
2992
3208
  async (opts) => {
2993
3209
  const logger = createLogger({ verbose: opts.verbose ?? false });
2994
3210
  const model = opts.model ?? getDefaultModel("transcribe");
2995
3211
  for (const f of opts.files) {
2996
- if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3212
+ if (!fs22.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
2997
3213
  }
2998
3214
  const results = [];
2999
3215
  for (let i = 0; i < opts.files.length; i++) {
@@ -3046,13 +3262,14 @@ function registerGeminiCommands(program2) {
3046
3262
  registerGeminiImageToImageCommand(gemini);
3047
3263
  registerGenerateVideoCommand(gemini);
3048
3264
  registerGeminiImageToVideoCommand(gemini);
3265
+ registerOmniVideoCommand(gemini);
3049
3266
  registerGeminiGenerateSpeechCommand(gemini);
3050
3267
  }
3051
3268
 
3052
3269
  // src/providers/leonardo/commands/generate.ts
3053
3270
  init_http_client();
3054
- import fs22 from "fs";
3055
- import path25 from "path";
3271
+ import fs23 from "fs";
3272
+ import path26 from "path";
3056
3273
 
3057
3274
  // src/providers/leonardo/client.ts
3058
3275
  init_errors();
@@ -3126,11 +3343,11 @@ var LeonardoClient = class {
3126
3343
  }
3127
3344
  apiKey;
3128
3345
  baseUrl;
3129
- async request(path45, opts = {}) {
3346
+ async request(path46, opts = {}) {
3130
3347
  const base = this.baseUrl.replace(/\/$/, "");
3131
- const v2Override = path45.startsWith("v2:");
3348
+ const v2Override = path46.startsWith("v2:");
3132
3349
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
3133
- const finalPath = v2Override ? path45.slice(3) : path45;
3350
+ const finalPath = v2Override ? path46.slice(3) : path46;
3134
3351
  const url = new URL(finalBase + finalPath);
3135
3352
  if (opts.query) {
3136
3353
  for (const [k, v] of Object.entries(opts.query)) {
@@ -3149,14 +3366,14 @@ var LeonardoClient = class {
3149
3366
  timeoutMs: opts.timeoutMs ?? 12e4
3150
3367
  });
3151
3368
  }
3152
- get(path45, query, logger) {
3153
- return this.request(path45, { method: "GET", query, logger });
3369
+ get(path46, query, logger) {
3370
+ return this.request(path46, { method: "GET", query, logger });
3154
3371
  }
3155
- post(path45, body, logger) {
3156
- return this.request(path45, { method: "POST", body, logger });
3372
+ post(path46, body, logger) {
3373
+ return this.request(path46, { method: "POST", body, logger });
3157
3374
  }
3158
- delete(path45, logger) {
3159
- return this.request(path45, { method: "DELETE", logger });
3375
+ delete(path46, logger) {
3376
+ return this.request(path46, { method: "DELETE", logger });
3160
3377
  }
3161
3378
  };
3162
3379
  function createLeonardoClient() {
@@ -3167,7 +3384,7 @@ function createLeonardoClient() {
3167
3384
  function extFromUrl(url, fallback = ".png") {
3168
3385
  try {
3169
3386
  const u = new URL(url);
3170
- const e = path25.extname(u.pathname);
3387
+ const e = path26.extname(u.pathname);
3171
3388
  return e.length > 0 ? e : fallback;
3172
3389
  } catch {
3173
3390
  return fallback;
@@ -3252,7 +3469,7 @@ function registerLeonardoGenerateCommand(parent) {
3252
3469
  if (!img) continue;
3253
3470
  const ext = extFromUrl(img.url);
3254
3471
  const filename = autoFilename(generationId, i + 1, ext);
3255
- const filepath = path25.join(outDir, filename);
3472
+ const filepath = path26.join(outDir, filename);
3256
3473
  try {
3257
3474
  await downloadFile(img.url, filepath);
3258
3475
  saved.push(filepath);
@@ -3262,9 +3479,9 @@ function registerLeonardoGenerateCommand(parent) {
3262
3479
  }
3263
3480
  }
3264
3481
  if (opts.output && saved[0]) {
3265
- const dest = path25.resolve(opts.output);
3266
- fs22.mkdirSync(path25.dirname(dest), { recursive: true });
3267
- fs22.copyFileSync(saved[0], dest);
3482
+ const dest = path26.resolve(opts.output);
3483
+ fs23.mkdirSync(path26.dirname(dest), { recursive: true });
3484
+ fs23.copyFileSync(saved[0], dest);
3268
3485
  logger.success(`Copied to ${dest}`);
3269
3486
  }
3270
3487
  console.log(`
@@ -3276,11 +3493,11 @@ Generated ${saved.length} image(s):`);
3276
3493
 
3277
3494
  // src/providers/leonardo/commands/image-to-image.ts
3278
3495
  init_http_client();
3279
- import fs23 from "fs";
3280
- import path26 from "path";
3496
+ import fs24 from "fs";
3497
+ import path27 from "path";
3281
3498
  function extFromUrl2(url, fallback = ".png") {
3282
3499
  try {
3283
- return path26.extname(new URL(url).pathname) || fallback;
3500
+ return path27.extname(new URL(url).pathname) || fallback;
3284
3501
  } catch {
3285
3502
  return fallback;
3286
3503
  }
@@ -3342,7 +3559,7 @@ function registerLeonardoImageToImageCommand(parent) {
3342
3559
  if (!img) continue;
3343
3560
  const ext = extFromUrl2(img.url);
3344
3561
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
3345
- const filepath = path26.join(outDir, filename);
3562
+ const filepath = path27.join(outDir, filename);
3346
3563
  try {
3347
3564
  await downloadFile(img.url, filepath);
3348
3565
  saved.push(filepath);
@@ -3352,9 +3569,9 @@ function registerLeonardoImageToImageCommand(parent) {
3352
3569
  }
3353
3570
  }
3354
3571
  if (opts.output && saved[0]) {
3355
- const dest = path26.resolve(opts.output);
3356
- fs23.mkdirSync(path26.dirname(dest), { recursive: true });
3357
- fs23.copyFileSync(saved[0], dest);
3572
+ const dest = path27.resolve(opts.output);
3573
+ fs24.mkdirSync(path27.dirname(dest), { recursive: true });
3574
+ fs24.copyFileSync(saved[0], dest);
3358
3575
  logger.success(`Copied to ${dest}`);
3359
3576
  }
3360
3577
  console.log(`
@@ -3366,7 +3583,7 @@ Generated ${saved.length} image(s):`);
3366
3583
 
3367
3584
  // src/providers/leonardo/commands/video.ts
3368
3585
  init_http_client();
3369
- import path27 from "path";
3586
+ import path28 from "path";
3370
3587
  function v2DimsFor(resolution) {
3371
3588
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
3372
3589
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -3477,7 +3694,7 @@ async function pollAndDownload(client, jobId, opts) {
3477
3694
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
3478
3695
  process.exit(1);
3479
3696
  }
3480
- const outPath = opts.output ?? path27.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
3697
+ const outPath = opts.output ?? path28.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
3481
3698
  await downloadFile(videoUrl, outPath);
3482
3699
  logger.success(`Saved ${outPath}`);
3483
3700
  await maybeDownloadThumb(gen, outPath, {
@@ -3584,7 +3801,7 @@ function registerLeonardoModelsCommand(parent) {
3584
3801
 
3585
3802
  // src/providers/leonardo/commands/status.ts
3586
3803
  init_http_client();
3587
- import path28 from "path";
3804
+ import path29 from "path";
3588
3805
  var VIDEO_KEYS = ["motionMP4URL", "url"];
3589
3806
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
3590
3807
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -3649,7 +3866,7 @@ function registerLeonardoStatusCommand(parent) {
3649
3866
  const outDir = getOutputDir();
3650
3867
  let idx = 0;
3651
3868
  for (const url of videos) {
3652
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
3869
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path29.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
3653
3870
  await downloadFile(url, dest);
3654
3871
  logger.success(`Saved ${dest}`);
3655
3872
  await maybeDownloadThumb(gen, dest, {
@@ -3662,7 +3879,7 @@ function registerLeonardoStatusCommand(parent) {
3662
3879
  for (const url of images) {
3663
3880
  const m = url.match(IMAGE_EXT_RE2);
3664
3881
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
3665
- const dest = path28.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
3882
+ const dest = path29.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
3666
3883
  await downloadFile(url, dest);
3667
3884
  logger.success(`Saved ${dest}`);
3668
3885
  idx++;
@@ -3750,7 +3967,7 @@ function requireMinimaxKey() {
3750
3967
  }
3751
3968
  return key;
3752
3969
  }
3753
- function authHeaders3(apiKey) {
3970
+ function authHeaders2(apiKey) {
3754
3971
  return {
3755
3972
  Authorization: `Bearer ${apiKey}`,
3756
3973
  "Content-Type": "application/json"
@@ -3762,7 +3979,7 @@ async function apiPost(endpoint2, payload, apiKey, opts = {}) {
3762
3979
  const data = await httpJson({
3763
3980
  url,
3764
3981
  method: "POST",
3765
- headers: authHeaders3(apiKey),
3982
+ headers: authHeaders2(apiKey),
3766
3983
  body: payload,
3767
3984
  timeoutMs: opts.timeoutMs ?? 12e4
3768
3985
  });
@@ -3778,7 +3995,7 @@ async function apiPost(endpoint2, payload, apiKey, opts = {}) {
3778
3995
  async function apiGet2(endpoint2, params, apiKey) {
3779
3996
  const url = new URL(`${MINIMAX_BASE_URL}/${endpoint2}`);
3780
3997
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
3781
- return httpJson({ url: url.toString(), headers: authHeaders3(apiKey) });
3998
+ return httpJson({ url: url.toString(), headers: authHeaders2(apiKey) });
3782
3999
  }
3783
4000
  async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
3784
4001
  const { intervalMs = 1e4, timeoutMs = 6e5, logger } = opts;
@@ -3794,7 +4011,7 @@ async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
3794
4011
  if (status === "Failed" || status === "Error") {
3795
4012
  throw new ProviderError(`Task ${taskId} failed: ${JSON.stringify(result)}`, "minimax");
3796
4013
  }
3797
- await sleep4(intervalMs);
4014
+ await sleep5(intervalMs);
3798
4015
  elapsed += intervalMs;
3799
4016
  }
3800
4017
  throw new ProviderError(`Task ${taskId} timed out after ${timeoutMs / 1e3}s`, "minimax");
@@ -3813,14 +4030,14 @@ async function downloadMinimaxFile(fileId, apiKey, dest, logger) {
3813
4030
  await downloadFile(downloadUrl, dest);
3814
4031
  return dest;
3815
4032
  }
3816
- function sleep4(ms) {
4033
+ function sleep5(ms) {
3817
4034
  return new Promise((r) => setTimeout(r, ms));
3818
4035
  }
3819
4036
 
3820
4037
  // src/providers/minimax/generators/music.ts
3821
4038
  init_http_client();
3822
- import fs24 from "fs";
3823
- import path29 from "path";
4039
+ import fs25 from "fs";
4040
+ import path30 from "path";
3824
4041
  async function generateMinimaxMusic(opts) {
3825
4042
  const {
3826
4043
  apiKey,
@@ -3865,14 +4082,14 @@ async function generateMinimaxMusic(opts) {
3865
4082
  }
3866
4083
  const durationMs = resp.extra_info?.music_duration ?? 0;
3867
4084
  const outDir = getOutputDir();
3868
- const dest = path29.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
4085
+ const dest = path30.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
3869
4086
  try {
3870
4087
  if (audioData.startsWith("http")) {
3871
4088
  const bytes = await fetchBytes(audioData);
3872
- fs24.writeFileSync(dest, bytes);
4089
+ fs25.writeFileSync(dest, bytes);
3873
4090
  } else {
3874
4091
  const audioBytes = Buffer.from(audioData, "hex");
3875
- fs24.writeFileSync(dest, audioBytes);
4092
+ fs25.writeFileSync(dest, audioBytes);
3876
4093
  }
3877
4094
  } catch (e) {
3878
4095
  return {
@@ -3883,8 +4100,8 @@ async function generateMinimaxMusic(opts) {
3883
4100
  const durSec = durationMs / 1e3;
3884
4101
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
3885
4102
  if (output) {
3886
- fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
3887
- fs24.copyFileSync(dest, output);
4103
+ fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4104
+ fs25.copyFileSync(dest, output);
3888
4105
  }
3889
4106
  return { status: "success", generatedAudio: dest, durationMs, model };
3890
4107
  }
@@ -3958,8 +4175,8 @@ Generated music: ${result.generatedAudio}`);
3958
4175
  init_errors();
3959
4176
 
3960
4177
  // src/providers/minimax/generators/speech.ts
3961
- import fs25 from "fs";
3962
- import path30 from "path";
4178
+ import fs26 from "fs";
4179
+ import path31 from "path";
3963
4180
  async function generateMinimaxSpeech(opts) {
3964
4181
  const {
3965
4182
  apiKey,
@@ -4006,12 +4223,12 @@ async function generateMinimaxSpeech(opts) {
4006
4223
  const audioBytes = Buffer.from(audioHex, "hex");
4007
4224
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
4008
4225
  const outDir = getOutputDir();
4009
- const dest = path30.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4010
- fs25.writeFileSync(dest, audioBytes);
4226
+ const dest = path31.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
4227
+ fs26.writeFileSync(dest, audioBytes);
4011
4228
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
4012
4229
  if (output) {
4013
- fs25.mkdirSync(path30.dirname(path30.resolve(output)), { recursive: true });
4014
- fs25.copyFileSync(dest, output);
4230
+ fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4231
+ fs26.copyFileSync(dest, output);
4015
4232
  }
4016
4233
  return { status: "success", generatedAudio: dest, model };
4017
4234
  }
@@ -4058,8 +4275,8 @@ Generated audio: ${result.generatedAudio}`);
4058
4275
  init_errors();
4059
4276
 
4060
4277
  // src/providers/minimax/generators/video.ts
4061
- import fs26 from "fs";
4062
- import path31 from "path";
4278
+ import fs27 from "fs";
4279
+ import path32 from "path";
4063
4280
  async function generateMinimaxVideo(opts) {
4064
4281
  const {
4065
4282
  apiKey,
@@ -4105,18 +4322,18 @@ async function generateMinimaxVideo(opts) {
4105
4322
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
4106
4323
  }
4107
4324
  const outDir = getOutputDir();
4108
- const dest = path31.join(outDir, `minimax_video_${Date.now()}.mp4`);
4325
+ const dest = path32.join(outDir, `minimax_video_${Date.now()}.mp4`);
4109
4326
  try {
4110
4327
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
4111
4328
  } catch (e) {
4112
4329
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
4113
4330
  }
4114
4331
  const elapsed = (Date.now() - start) / 1e3;
4115
- const stat = fs26.statSync(dest);
4332
+ const stat = fs27.statSync(dest);
4116
4333
  const fileSizeMb = stat.size / (1024 * 1024);
4117
4334
  if (output) {
4118
- fs26.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
4119
- fs26.copyFileSync(dest, output);
4335
+ fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4336
+ fs27.copyFileSync(dest, output);
4120
4337
  }
4121
4338
  await maybeDownloadThumb(pollResult, dest, {
4122
4339
  skip: thumb === false,
@@ -4179,8 +4396,8 @@ Generated video: ${result.generatedVideo}`);
4179
4396
  // src/providers/minimax/generators/image.ts
4180
4397
  init_errors();
4181
4398
  init_http_client();
4182
- import fs27 from "fs";
4183
- import path32 from "path";
4399
+ import fs28 from "fs";
4400
+ import path33 from "path";
4184
4401
  async function generateMinimaxImage(opts) {
4185
4402
  const {
4186
4403
  apiKey,
@@ -4218,8 +4435,8 @@ async function generateMinimaxImage(opts) {
4218
4435
  try {
4219
4436
  const bytes = await fetchBytes(url);
4220
4437
  const fname = `minimax_image_${Date.now()}_${i}.png`;
4221
- const dest = path32.join(outDir, fname);
4222
- fs27.writeFileSync(dest, bytes);
4438
+ const dest = path33.join(outDir, fname);
4439
+ fs28.writeFileSync(dest, bytes);
4223
4440
  savedFiles.push(dest);
4224
4441
  logger?.success(`Saved: ${dest}`);
4225
4442
  } catch (e) {
@@ -4230,8 +4447,8 @@ async function generateMinimaxImage(opts) {
4230
4447
  }
4231
4448
  }
4232
4449
  if (output && savedFiles[0]) {
4233
- fs27.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
4234
- fs27.copyFileSync(savedFiles[0], output);
4450
+ fs28.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
4451
+ fs28.copyFileSync(savedFiles[0], output);
4235
4452
  }
4236
4453
  return { status: "success", generatedImages: savedFiles, model };
4237
4454
  }
@@ -4267,8 +4484,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
4267
4484
  // src/providers/minimax/commands/image-to-image.ts
4268
4485
  init_errors();
4269
4486
  init_http_client();
4270
- import fs28 from "fs";
4271
- import path33 from "path";
4487
+ import fs29 from "fs";
4488
+ import path34 from "path";
4272
4489
  function registerMinimaxImageToImageCommand(parent) {
4273
4490
  parent.command("image-to-image").alias("i2i").description(
4274
4491
  "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."
@@ -4323,8 +4540,8 @@ function registerMinimaxImageToImageCommand(parent) {
4323
4540
  if (!url) continue;
4324
4541
  try {
4325
4542
  const bytes = await fetchBytes(url);
4326
- const dest = path33.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4327
- fs28.writeFileSync(dest, bytes);
4543
+ const dest = path34.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
4544
+ fs29.writeFileSync(dest, bytes);
4328
4545
  saved.push(dest);
4329
4546
  logger.success(`Saved: ${dest}`);
4330
4547
  } catch (e) {
@@ -4335,8 +4552,8 @@ function registerMinimaxImageToImageCommand(parent) {
4335
4552
  }
4336
4553
  }
4337
4554
  if (opts.output && saved[0]) {
4338
- fs28.mkdirSync(path33.dirname(path33.resolve(opts.output)), { recursive: true });
4339
- fs28.copyFileSync(saved[0], opts.output);
4555
+ fs29.mkdirSync(path34.dirname(path34.resolve(opts.output)), { recursive: true });
4556
+ fs29.copyFileSync(saved[0], opts.output);
4340
4557
  logger.success(`Copied to: ${opts.output}`);
4341
4558
  }
4342
4559
  console.log(`
@@ -4470,12 +4687,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
4470
4687
 
4471
4688
  // src/providers/openai/openai-audio.ts
4472
4689
  init_http_client();
4473
- import fs30 from "fs";
4474
- import path35 from "path";
4690
+ import fs31 from "fs";
4691
+ import path36 from "path";
4475
4692
 
4476
4693
  // src/providers/openai/client.ts
4477
- import fs29 from "fs";
4478
- import path34 from "path";
4694
+ import fs30 from "fs";
4695
+ import path35 from "path";
4479
4696
  init_errors();
4480
4697
  init_http_client();
4481
4698
  var DEFAULT_TIMEOUT_MS3 = 24e4;
@@ -4491,14 +4708,14 @@ function requireOpenAIKey() {
4491
4708
  function endpoint(pathname) {
4492
4709
  return `${OPENAI_BASE_URL}/${pathname.replace(/^\//, "")}`;
4493
4710
  }
4494
- function authHeaders4(apiKey, extra) {
4711
+ function authHeaders3(apiKey, extra) {
4495
4712
  return { Authorization: `Bearer ${apiKey}`, ...extra ?? {} };
4496
4713
  }
4497
4714
  function postOpenAIJson(pathname, body, apiKey, timeoutMs = DEFAULT_TIMEOUT_MS3) {
4498
4715
  return httpJson({
4499
4716
  url: endpoint(pathname),
4500
4717
  method: "POST",
4501
- headers: authHeaders4(apiKey, { "Content-Type": "application/json" }),
4718
+ headers: authHeaders3(apiKey, { "Content-Type": "application/json" }),
4502
4719
  body,
4503
4720
  timeoutMs
4504
4721
  });
@@ -4509,7 +4726,7 @@ async function postOpenAIForm(pathname, form, apiKey, timeoutMs = DEFAULT_TIMEOU
4509
4726
  try {
4510
4727
  res = await globalThis.fetch(url, {
4511
4728
  method: "POST",
4512
- headers: authHeaders4(apiKey),
4729
+ headers: authHeaders3(apiKey),
4513
4730
  body: form,
4514
4731
  signal: AbortSignal.timeout(timeoutMs)
4515
4732
  });
@@ -4528,7 +4745,7 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
4528
4745
  try {
4529
4746
  res = await globalThis.fetch(url, {
4530
4747
  method: "POST",
4531
- headers: authHeaders4(apiKey, { "Content-Type": "application/json" }),
4748
+ headers: authHeaders3(apiKey, { "Content-Type": "application/json" }),
4532
4749
  body: JSON.stringify(body),
4533
4750
  signal: AbortSignal.timeout(timeoutMs)
4534
4751
  });
@@ -4542,10 +4759,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
4542
4759
  return Buffer.from(await res.arrayBuffer());
4543
4760
  }
4544
4761
  function fileFromPath(filePath) {
4545
- const abs = path34.resolve(filePath);
4546
- if (!fs29.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4547
- const bytes = fs29.readFileSync(abs);
4548
- return new File([new Uint8Array(bytes)], path34.basename(abs));
4762
+ const abs = path35.resolve(filePath);
4763
+ if (!fs30.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
4764
+ const bytes = fs30.readFileSync(abs);
4765
+ return new File([new Uint8Array(bytes)], path35.basename(abs));
4549
4766
  }
4550
4767
 
4551
4768
  // src/providers/openai/openai-audio.ts
@@ -4584,7 +4801,7 @@ async function resolveAudioReferenceDataUrl(ref) {
4584
4801
  }
4585
4802
  function audioMimeFromReference(ref) {
4586
4803
  const pathname = new URL(ref).pathname;
4587
- const ext = path35.extname(pathname).toLowerCase();
4804
+ const ext = path36.extname(pathname).toLowerCase();
4588
4805
  switch (ext) {
4589
4806
  case ".mp3":
4590
4807
  return "audio/mpeg";
@@ -4603,12 +4820,12 @@ function audioMimeFromReference(ref) {
4603
4820
  async function generateOpenAISpeech(opts) {
4604
4821
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
4605
4822
  const ext = extFromAudioFormat(opts.outputFormat);
4606
- const dest = path35.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4607
- fs30.writeFileSync(dest, bytes);
4823
+ const dest = path36.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
4824
+ fs31.writeFileSync(dest, bytes);
4608
4825
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
4609
4826
  if (opts.output) {
4610
- fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
4611
- fs30.copyFileSync(dest, opts.output);
4827
+ fs31.mkdirSync(path36.dirname(path36.resolve(opts.output)), { recursive: true });
4828
+ fs31.copyFileSync(dest, opts.output);
4612
4829
  }
4613
4830
  return dest;
4614
4831
  }
@@ -4630,9 +4847,9 @@ async function transcribeOpenAIAudio(opts) {
4630
4847
  );
4631
4848
  const text = typeof data.text === "string" ? data.text : void 0;
4632
4849
  const ext = opts.format === "text" ? "txt" : "json";
4633
- const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
4634
- fs30.mkdirSync(path35.dirname(dest), { recursive: true });
4635
- fs30.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
4850
+ const dest = opts.output ? path36.resolve(opts.output) : path36.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
4851
+ fs31.mkdirSync(path36.dirname(dest), { recursive: true });
4852
+ fs31.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
4636
4853
  opts.logger?.success(`Saved transcript: ${dest}`);
4637
4854
  return { path: dest, text };
4638
4855
  }
@@ -4659,9 +4876,9 @@ Generated audio: ${dest}`);
4659
4876
  }
4660
4877
 
4661
4878
  // src/providers/openai/codex-image-driver.ts
4662
- import fs31 from "fs";
4879
+ import fs32 from "fs";
4663
4880
  import os2 from "os";
4664
- import path36 from "path";
4881
+ import path37 from "path";
4665
4882
  import { execa } from "execa";
4666
4883
  init_errors();
4667
4884
  var defaultRunner = async (cmd, args, opts) => {
@@ -4710,12 +4927,12 @@ function sanitizeCodexEnv() {
4710
4927
  async function runCodexImageGeneration(opts) {
4711
4928
  const runner = opts.runner ?? defaultRunner;
4712
4929
  const ext = extFromImageFormat(opts.outputFormat);
4713
- const dest = opts.output ? path36.resolve(opts.output) : path36.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
4714
- fs31.mkdirSync(path36.dirname(dest), { recursive: true });
4715
- const tmp = fs31.mkdtempSync(path36.join(os2.tmpdir(), "multix-codex-image-"));
4716
- const schema = path36.join(tmp, "schema.json");
4717
- const result = path36.join(tmp, "result.json");
4718
- fs31.writeFileSync(
4930
+ const dest = opts.output ? path37.resolve(opts.output) : path37.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
4931
+ fs32.mkdirSync(path37.dirname(dest), { recursive: true });
4932
+ const tmp = fs32.mkdtempSync(path37.join(os2.tmpdir(), "multix-codex-image-"));
4933
+ const schema = path37.join(tmp, "schema.json");
4934
+ const result = path37.join(tmp, "result.json");
4935
+ fs32.writeFileSync(
4719
4936
  schema,
4720
4937
  JSON.stringify({
4721
4938
  type: "object",
@@ -4745,7 +4962,7 @@ async function runCodexImageGeneration(opts) {
4745
4962
  if (res.exitCode !== 0) {
4746
4963
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
4747
4964
  }
4748
- if (!fs31.existsSync(dest) || fs31.statSync(dest).size === 0) {
4965
+ if (!fs32.existsSync(dest) || fs32.statSync(dest).size === 0) {
4749
4966
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
4750
4967
  }
4751
4968
  return { path: dest };
@@ -4763,8 +4980,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
4763
4980
  // src/providers/openai/openai-image.ts
4764
4981
  init_errors();
4765
4982
  init_http_client();
4766
- import fs32 from "fs";
4767
- import path37 from "path";
4983
+ import fs33 from "fs";
4984
+ import path38 from "path";
4768
4985
  function buildImageGenerationBody(opts) {
4769
4986
  return {
4770
4987
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -4797,14 +5014,14 @@ function saveOpenAIImages(opts) {
4797
5014
  for (let i = 0; i < items.length; i++) {
4798
5015
  const b64 = items[i]?.b64_json;
4799
5016
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
4800
- const dest = path37.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
4801
- fs32.writeFileSync(dest, Buffer.from(b64, "base64"));
5017
+ const dest = path38.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
5018
+ fs33.writeFileSync(dest, Buffer.from(b64, "base64"));
4802
5019
  saved.push(dest);
4803
5020
  opts.logger?.success(`Saved: ${dest}`);
4804
5021
  }
4805
5022
  if (opts.output && saved[0]) {
4806
- fs32.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
4807
- fs32.copyFileSync(saved[0], opts.output);
5023
+ fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5024
+ fs33.copyFileSync(saved[0], opts.output);
4808
5025
  opts.logger?.success(`Copied to: ${opts.output}`);
4809
5026
  }
4810
5027
  return saved;
@@ -4813,7 +5030,7 @@ async function resolveImageRefBytes(ref) {
4813
5030
  if (isHttpUrl(ref)) {
4814
5031
  const bytes2 = await fetchBytes(ref);
4815
5032
  const pathname = new URL(ref).pathname;
4816
- const filename = path37.basename(pathname) || "reference.png";
5033
+ const filename = path38.basename(pathname) || "reference.png";
4817
5034
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
4818
5035
  }
4819
5036
  const file = fileFromPath(ref);
@@ -4858,7 +5075,7 @@ function sniffImageMime2(buf, name) {
4858
5075
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
4859
5076
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
4860
5077
  return "image/webp";
4861
- const ext = path37.extname(name).toLowerCase();
5078
+ const ext = path38.extname(name).toLowerCase();
4862
5079
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
4863
5080
  if (ext === ".webp") return "image/webp";
4864
5081
  return "image/png";
@@ -4984,8 +5201,8 @@ function registerOpenAICommands(program2) {
4984
5201
  }
4985
5202
 
4986
5203
  // src/providers/openrouter/client.ts
4987
- import fs33 from "fs";
4988
- import path38 from "path";
5204
+ import fs34 from "fs";
5205
+ import path39 from "path";
4989
5206
  init_errors();
4990
5207
  init_http_client();
4991
5208
 
@@ -5150,8 +5367,8 @@ async function generateOpenRouterImage(opts) {
5150
5367
  if (!url) continue;
5151
5368
  try {
5152
5369
  const bytes = await fetchBytes(url);
5153
- const dest = path38.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5154
- fs33.writeFileSync(dest, bytes);
5370
+ const dest = path39.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
5371
+ fs34.writeFileSync(dest, bytes);
5155
5372
  savedFiles.push(dest);
5156
5373
  logger?.success(`Saved: ${dest}`);
5157
5374
  } catch (e) {
@@ -5162,8 +5379,8 @@ async function generateOpenRouterImage(opts) {
5162
5379
  }
5163
5380
  }
5164
5381
  if (output && savedFiles[0]) {
5165
- fs33.mkdirSync(path38.dirname(path38.resolve(output)), { recursive: true });
5166
- fs33.writeFileSync(output, fs33.readFileSync(savedFiles[0]));
5382
+ fs34.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
5383
+ fs34.writeFileSync(output, fs34.readFileSync(savedFiles[0]));
5167
5384
  }
5168
5385
  return { status: "success", generatedImages: savedFiles, model: usedModel };
5169
5386
  }
@@ -5198,8 +5415,8 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
5198
5415
  }
5199
5416
 
5200
5417
  // src/providers/openrouter/commands/image-to-image.ts
5201
- import fs34 from "fs";
5202
- import path39 from "path";
5418
+ import fs35 from "fs";
5419
+ import path40 from "path";
5203
5420
  init_errors();
5204
5421
  init_http_client();
5205
5422
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
@@ -5289,14 +5506,14 @@ async function runImageToImage(opts) {
5289
5506
  const url = parsed.urls[i];
5290
5507
  if (!url) continue;
5291
5508
  const bytes = await fetchBytes(url);
5292
- const dest = path39.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5293
- fs34.writeFileSync(dest, bytes);
5509
+ const dest = path40.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
5510
+ fs35.writeFileSync(dest, bytes);
5294
5511
  saved.push(dest);
5295
5512
  opts.logger.success(`Saved: ${dest}`);
5296
5513
  }
5297
5514
  if (opts.output && saved[0]) {
5298
- fs34.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5299
- fs34.copyFileSync(saved[0], opts.output);
5515
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5516
+ fs35.copyFileSync(saved[0], opts.output);
5300
5517
  opts.logger.success(`Copied to: ${opts.output}`);
5301
5518
  }
5302
5519
  return saved;
@@ -5305,7 +5522,7 @@ async function runImageToImage(opts) {
5305
5522
  // src/providers/openrouter/video-client.ts
5306
5523
  init_http_client();
5307
5524
  var OPENROUTER_VIDEOS_URL = "https://openrouter.ai/api/v1/videos";
5308
- function authHeaders5(apiKey) {
5525
+ function authHeaders4(apiKey) {
5309
5526
  const headers = {
5310
5527
  Authorization: `Bearer ${apiKey}`,
5311
5528
  "Content-Type": "application/json"
@@ -5320,7 +5537,7 @@ async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
5320
5537
  return httpJson({
5321
5538
  url: OPENROUTER_VIDEOS_URL,
5322
5539
  method: "POST",
5323
- headers: authHeaders5(apiKey),
5540
+ headers: authHeaders4(apiKey),
5324
5541
  body,
5325
5542
  timeoutMs: 12e4
5326
5543
  });
@@ -5328,7 +5545,7 @@ async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
5328
5545
  async function pollVideoJob(jobId, apiKey = requireOpenRouterKey()) {
5329
5546
  return httpJson({
5330
5547
  url: `${OPENROUTER_VIDEOS_URL}/${jobId}`,
5331
- headers: authHeaders5(apiKey)
5548
+ headers: authHeaders4(apiKey)
5332
5549
  });
5333
5550
  }
5334
5551
  function videoContentUrl(jobId) {
@@ -5337,18 +5554,18 @@ function videoContentUrl(jobId) {
5337
5554
  async function listVideoModels(apiKey = requireOpenRouterKey()) {
5338
5555
  return httpJson({
5339
5556
  url: `${OPENROUTER_VIDEOS_URL}/models`,
5340
- headers: authHeaders5(apiKey)
5557
+ headers: authHeaders4(apiKey)
5341
5558
  });
5342
5559
  }
5343
5560
 
5344
5561
  // src/providers/openrouter/commands/video-status.ts
5345
- import fs35 from "fs";
5346
- import path40 from "path";
5562
+ import fs36 from "fs";
5563
+ import path41 from "path";
5347
5564
  async function downloadOpenRouterVideo(opts) {
5348
5565
  const { jobId, logger, source } = opts;
5349
5566
  const apiKey = requireOpenRouterKey();
5350
5567
  const url = videoContentUrl(jobId);
5351
- const dest = path40.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5568
+ const dest = path41.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
5352
5569
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
5353
5570
  if (!r.ok) {
5354
5571
  logger.error(`Download HTTP ${r.status}`);
@@ -5358,14 +5575,14 @@ async function downloadOpenRouterVideo(opts) {
5358
5575
  logger.error("Empty response body");
5359
5576
  process.exit(1);
5360
5577
  }
5361
- fs35.mkdirSync(path40.dirname(dest), { recursive: true });
5362
- const ws = fs35.createWriteStream(dest);
5578
+ fs36.mkdirSync(path41.dirname(dest), { recursive: true });
5579
+ const ws = fs36.createWriteStream(dest);
5363
5580
  const { pipeline: pipeline2 } = await import("stream/promises");
5364
5581
  await pipeline2(r.body, ws);
5365
5582
  logger.success(`Saved ${dest}`);
5366
5583
  if (opts.output) {
5367
- fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
5368
- fs35.copyFileSync(dest, opts.output);
5584
+ fs36.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
5585
+ fs36.copyFileSync(dest, opts.output);
5369
5586
  logger.success(`Copied to ${opts.output}`);
5370
5587
  }
5371
5588
  await maybeDownloadThumb(source, dest, {
@@ -5746,8 +5963,8 @@ function registerCheckCommand(program2) {
5746
5963
 
5747
5964
  // src/commands/doc/convert.ts
5748
5965
  init_errors();
5749
- import fs36 from "fs";
5750
- import path41 from "path";
5966
+ import fs37 from "fs";
5967
+ import path42 from "path";
5751
5968
 
5752
5969
  // src/commands/doc/default-prompt.ts
5753
5970
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -5768,18 +5985,18 @@ async function convertDocuments(opts) {
5768
5985
  const model = opts.model ?? DOC_MODEL_DEFAULT;
5769
5986
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
5770
5987
  for (const f of inputs) {
5771
- if (!fs36.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
5988
+ if (!fs37.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
5772
5989
  }
5773
5990
  let outputPath;
5774
5991
  if (output) {
5775
- outputPath = path41.resolve(output);
5992
+ outputPath = path42.resolve(output);
5776
5993
  } else if (autoName && inputs.length === 1 && inputs[0]) {
5777
- const base = path41.basename(inputs[0], path41.extname(inputs[0]));
5778
- outputPath = path41.join(getOutputDir(), `${base}-extraction.md`);
5994
+ const base = path42.basename(inputs[0], path42.extname(inputs[0]));
5995
+ outputPath = path42.join(getOutputDir(), `${base}-extraction.md`);
5779
5996
  } else {
5780
- outputPath = path41.join(getOutputDir(), "document-extraction.md");
5997
+ outputPath = path42.join(getOutputDir(), "document-extraction.md");
5781
5998
  }
5782
- fs36.mkdirSync(path41.dirname(outputPath), { recursive: true });
5999
+ fs37.mkdirSync(path42.dirname(outputPath), { recursive: true });
5783
6000
  const results = [];
5784
6001
  for (let i = 0; i < inputs.length; i++) {
5785
6002
  const filePath = inputs[i];
@@ -5811,7 +6028,7 @@ async function convertDocuments(opts) {
5811
6028
  if (attempt < 2) {
5812
6029
  const wait = 2 ** attempt * 1e3;
5813
6030
  logger?.debug(`Retry ${attempt + 1} after ${wait / 1e3}s: ${errorMsg}`);
5814
- await sleep5(wait);
6031
+ await sleep6(wait);
5815
6032
  }
5816
6033
  }
5817
6034
  }
@@ -5830,7 +6047,7 @@ async function convertDocuments(opts) {
5830
6047
  `);
5831
6048
  for (const r of results) {
5832
6049
  lines.push(`
5833
- ## ${path41.basename(r.file)}
6050
+ ## ${path42.basename(r.file)}
5834
6051
  `);
5835
6052
  if (r.status === "success" && r.markdown) {
5836
6053
  lines.push(`${r.markdown}
@@ -5843,7 +6060,7 @@ async function convertDocuments(opts) {
5843
6060
  }
5844
6061
  lines.push("\n---\n");
5845
6062
  }
5846
- fs36.writeFileSync(outputPath, lines.join("\n"), "utf8");
6063
+ fs37.writeFileSync(outputPath, lines.join("\n"), "utf8");
5847
6064
  const succeeded = results.filter((r) => r.status === "success").length;
5848
6065
  console.log(`
5849
6066
  ${"=".repeat(50)}`);
@@ -5859,7 +6076,7 @@ ${"=".repeat(50)}`);
5859
6076
  const failed = results.filter((r) => r.status === "error").length;
5860
6077
  if (failed > 0) process.exit(1);
5861
6078
  }
5862
- function sleep5(ms) {
6079
+ function sleep6(ms) {
5863
6080
  return new Promise((r) => setTimeout(r, ms));
5864
6081
  }
5865
6082
 
@@ -5889,12 +6106,12 @@ function registerDocCommands(program2) {
5889
6106
 
5890
6107
  // src/commands/media/index.ts
5891
6108
  init_errors();
5892
- import fs39 from "fs";
5893
- import path44 from "path";
6109
+ import fs40 from "fs";
6110
+ import path45 from "path";
5894
6111
 
5895
6112
  // src/commands/media/batch.ts
5896
- import fs37 from "fs";
5897
- import path42 from "path";
6113
+ import fs38 from "fs";
6114
+ import path43 from "path";
5898
6115
 
5899
6116
  // src/commands/media/detect-type.ts
5900
6117
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -6090,10 +6307,10 @@ async function batchOptimize(opts) {
6090
6307
  verbose,
6091
6308
  logger
6092
6309
  } = opts;
6093
- fs37.mkdirSync(outputDir, { recursive: true });
6094
- const entries = fs37.readdirSync(inputDir);
6310
+ fs38.mkdirSync(outputDir, { recursive: true });
6311
+ const entries = fs38.readdirSync(inputDir);
6095
6312
  const mediaFiles = entries.filter((f) => {
6096
- const ext = path42.extname(f).toLowerCase();
6313
+ const ext = path43.extname(f).toLowerCase();
6097
6314
  return ALL_EXTS.has(ext);
6098
6315
  });
6099
6316
  if (mediaFiles.length === 0) {
@@ -6104,9 +6321,9 @@ async function batchOptimize(opts) {
6104
6321
  let succeeded = 0;
6105
6322
  const errors = [];
6106
6323
  for (const file of mediaFiles) {
6107
- const inputPath = path42.join(inputDir, file);
6108
- const outputPath = path42.join(outputDir, file);
6109
- const ext = path42.extname(file).toLowerCase();
6324
+ const inputPath = path43.join(inputDir, file);
6325
+ const outputPath = path43.join(outputDir, file);
6326
+ const ext = path43.extname(file).toLowerCase();
6110
6327
  const kind = extToKind(ext);
6111
6328
  logger?.debug(`Processing: ${file}`);
6112
6329
  try {
@@ -6143,8 +6360,8 @@ async function batchOptimize(opts) {
6143
6360
 
6144
6361
  // src/commands/media/split-video.ts
6145
6362
  init_errors();
6146
- import fs38 from "fs";
6147
- import path43 from "path";
6363
+ import fs39 from "fs";
6364
+ import path44 from "path";
6148
6365
  async function splitVideo(opts) {
6149
6366
  await assertFfmpeg();
6150
6367
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -6155,10 +6372,10 @@ async function splitVideo(opts) {
6155
6372
  logger?.info("Video is short enough \u2014 no splitting needed");
6156
6373
  return [input];
6157
6374
  }
6158
- fs38.mkdirSync(outputDir, { recursive: true });
6159
- const ext = path43.extname(input) || ".mp4";
6160
- const baseName = path43.basename(input, ext);
6161
- const outputPattern = path43.join(outputDir, `${baseName}_chunk_%03d${ext}`);
6375
+ fs39.mkdirSync(outputDir, { recursive: true });
6376
+ const ext = path44.extname(input) || ".mp4";
6377
+ const baseName = path44.basename(input, ext);
6378
+ const outputPattern = path44.join(outputDir, `${baseName}_chunk_%03d${ext}`);
6162
6379
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
6163
6380
  const args = [
6164
6381
  "-i",
@@ -6177,8 +6394,8 @@ async function splitVideo(opts) {
6177
6394
  await runFfmpeg(args, { verbose, logger });
6178
6395
  const files = [];
6179
6396
  for (let i = 0; i < numChunks + 1; i++) {
6180
- const fname = path43.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6181
- if (fs38.existsSync(fname)) files.push(fname);
6397
+ const fname = path44.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
6398
+ if (fs39.existsSync(fname)) files.push(fname);
6182
6399
  }
6183
6400
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
6184
6401
  return files;
@@ -6192,9 +6409,9 @@ function registerMediaCommands(program2) {
6192
6409
  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(
6193
6410
  async (opts) => {
6194
6411
  const logger = createLogger({ verbose: opts.verbose ?? false });
6195
- if (!fs39.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6196
- fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6197
- const ext = path44.extname(opts.input).toLowerCase();
6412
+ if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6413
+ fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
6414
+ const ext = path45.extname(opts.input).toLowerCase();
6198
6415
  const kind = extToKind(ext);
6199
6416
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
6200
6417
  const quality = Number.parseInt(opts.quality, 10);
@@ -6234,7 +6451,7 @@ function registerMediaCommands(program2) {
6234
6451
  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(
6235
6452
  async (opts) => {
6236
6453
  const logger = createLogger({ verbose: opts.verbose ?? false });
6237
- if (!fs39.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6454
+ if (!fs40.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
6238
6455
  const chunks = await splitVideo({
6239
6456
  input: opts.input,
6240
6457
  outputDir: opts.outputDir,
@@ -6250,7 +6467,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
6250
6467
  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(
6251
6468
  async (opts) => {
6252
6469
  const logger = createLogger({ verbose: opts.verbose ?? false });
6253
- if (!fs39.existsSync(opts.inputDir))
6470
+ if (!fs40.existsSync(opts.inputDir))
6254
6471
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
6255
6472
  const result = await batchOptimize({
6256
6473
  inputDir: opts.inputDir,