@mrgoonie/multix 0.7.1 → 0.7.3

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
@@ -9,6 +9,28 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/core/redact.ts
13
+ function redact(key) {
14
+ if (key.length <= 10) return "***";
15
+ return `${key.slice(0, 6)}...${key.slice(-4)}`;
16
+ }
17
+ function redactUrl(url) {
18
+ try {
19
+ const u = new URL(url);
20
+ if (u.origin === "null") return `${u.protocol}[redacted]`;
21
+ const suffix = u.search || u.hash ? "?[redacted]" : "";
22
+ return `${u.origin}${u.pathname}${suffix}`;
23
+ } catch {
24
+ const cut = url.search(/[?#]/);
25
+ return cut === -1 ? url : `${url.slice(0, cut)}?[redacted]`;
26
+ }
27
+ }
28
+ var init_redact = __esm({
29
+ "src/core/redact.ts"() {
30
+ "use strict";
31
+ }
32
+ });
33
+
12
34
  // src/core/errors.ts
13
35
  var errors_exports = {};
14
36
  __export(errors_exports, {
@@ -22,6 +44,7 @@ var MultixError, ConfigError, ProviderError, ValidationError, HttpError;
22
44
  var init_errors = __esm({
23
45
  "src/core/errors.ts"() {
24
46
  "use strict";
47
+ init_redact();
25
48
  MultixError = class extends Error {
26
49
  constructor(message, code) {
27
50
  super(message);
@@ -54,117 +77,24 @@ var init_errors = __esm({
54
77
  }
55
78
  };
56
79
  HttpError = class extends MultixError {
57
- constructor(status, snippet, url) {
58
- super(`HTTP ${status} from ${url}: ${snippet}`, "HTTP_ERROR");
80
+ constructor(status, snippet, url, retryAfterMs) {
81
+ super(`HTTP ${status} from ${redactUrl(url)}: ${snippet}`, "HTTP_ERROR");
59
82
  this.status = status;
60
83
  this.snippet = snippet;
61
- this.url = url;
84
+ this.retryAfterMs = retryAfterMs;
85
+ this.url = redactUrl(url);
86
+ Object.defineProperty(this, "rawUrl", { value: url, enumerable: false });
62
87
  this.name = "HttpError";
63
88
  }
64
89
  status;
65
90
  snippet;
91
+ retryAfterMs;
92
+ /** Request URL with query string and fragment redacted — safe to log or serialize. */
66
93
  url;
67
94
  };
68
95
  }
69
96
  });
70
97
 
71
- // src/core/http-client.ts
72
- var http_client_exports = {};
73
- __export(http_client_exports, {
74
- downloadFile: () => downloadFile,
75
- fetchBytes: () => fetchBytes,
76
- httpJson: () => httpJson
77
- });
78
- import { once } from "events";
79
- import fs4 from "fs";
80
- import path4 from "path";
81
- import { pipeline } from "stream/promises";
82
- async function httpJson(opts) {
83
- const { url, method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
84
- const init = {
85
- method,
86
- headers: {
87
- "Content-Type": "application/json",
88
- ...headers
89
- },
90
- signal: AbortSignal.timeout(timeoutMs)
91
- };
92
- if (body !== void 0) {
93
- init.body = JSON.stringify(body);
94
- }
95
- let response;
96
- try {
97
- response = await globalThis.fetch(url, init);
98
- } catch (cause) {
99
- const msg = cause instanceof Error ? cause.message : String(cause);
100
- throw new HttpError(0, msg, url);
101
- }
102
- if (!response.ok) {
103
- let snippet = "";
104
- try {
105
- const text = await response.text();
106
- snippet = text.slice(0, SNIPPET_MAX);
107
- } catch {
108
- snippet = "(could not read body)";
109
- }
110
- throw new HttpError(response.status, snippet, url);
111
- }
112
- return response.json();
113
- }
114
- async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
115
- fs4.mkdirSync(path4.dirname(dest), { recursive: true });
116
- let response;
117
- try {
118
- response = await globalThis.fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
119
- } catch (cause) {
120
- const msg = cause instanceof Error ? cause.message : String(cause);
121
- throw new HttpError(0, msg, url);
122
- }
123
- if (!response.ok) {
124
- throw new HttpError(response.status, "(download failed)", url);
125
- }
126
- if (!response.body) {
127
- throw new HttpError(0, "Response body is null", url);
128
- }
129
- const fileStream = fs4.createWriteStream(dest);
130
- try {
131
- await pipeline(response.body, fileStream);
132
- } catch (err2) {
133
- if (!fileStream.closed) await once(fileStream, "close");
134
- throw err2;
135
- }
136
- }
137
- async function fetchBytes(urlOrDataUri) {
138
- if (urlOrDataUri.startsWith("data:")) {
139
- const commaIdx = urlOrDataUri.indexOf(",");
140
- if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
141
- const encoded = urlOrDataUri.slice(commaIdx + 1);
142
- return Buffer.from(encoded, "base64");
143
- }
144
- let response;
145
- try {
146
- response = await globalThis.fetch(urlOrDataUri, {
147
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
148
- });
149
- } catch (cause) {
150
- const msg = cause instanceof Error ? cause.message : String(cause);
151
- throw new HttpError(0, msg, urlOrDataUri);
152
- }
153
- if (!response.ok) {
154
- throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
155
- }
156
- return Buffer.from(await response.arrayBuffer());
157
- }
158
- var DEFAULT_TIMEOUT_MS, SNIPPET_MAX;
159
- var init_http_client = __esm({
160
- "src/core/http-client.ts"() {
161
- "use strict";
162
- init_errors();
163
- DEFAULT_TIMEOUT_MS = 12e4;
164
- SNIPPET_MAX = 500;
165
- }
166
- });
167
-
168
98
  // src/cli.ts
169
99
  import { createRequire as createRequire2 } from "module";
170
100
  import { Command } from "commander";
@@ -346,6 +276,7 @@ async function poll(opts) {
346
276
  }
347
277
 
348
278
  // src/core/env-loader.ts
279
+ init_redact();
349
280
  import fs3 from "fs";
350
281
  import os from "os";
351
282
  import path3 from "path";
@@ -368,14 +299,138 @@ function loadEnv() {
368
299
  function resolveKey(name) {
369
300
  return process.env[name] || void 0;
370
301
  }
371
- function redact(key) {
372
- if (key.length <= 10) return "***";
373
- return `${key.slice(0, 6)}...${key.slice(-4)}`;
374
- }
375
302
 
376
303
  // src/providers/byteplus/client.ts
377
304
  init_errors();
378
- init_http_client();
305
+
306
+ // src/core/http-client.ts
307
+ init_errors();
308
+ import { once } from "events";
309
+ import fs4 from "fs";
310
+ import path4 from "path";
311
+ import { pipeline } from "stream/promises";
312
+
313
+ // src/core/fetch-json.ts
314
+ init_errors();
315
+ var DEFAULT_TIMEOUT_MS = 12e4;
316
+ var SNIPPET_MAX = 500;
317
+ function parseRetryAfter(value, now = Date.now()) {
318
+ if (!value) return void 0;
319
+ const trimmed = value.trim();
320
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1e3);
321
+ const date = Date.parse(trimmed);
322
+ if (Number.isNaN(date)) return void 0;
323
+ return Math.max(0, date - now);
324
+ }
325
+ async function httpErrorFromResponse(response, url) {
326
+ let snippet = "";
327
+ try {
328
+ const text = await response.text();
329
+ snippet = text.slice(0, SNIPPET_MAX);
330
+ } catch {
331
+ snippet = "(could not read body)";
332
+ }
333
+ return new HttpError(
334
+ response.status,
335
+ snippet,
336
+ url,
337
+ parseRetryAfter(response.headers.get("retry-after"))
338
+ );
339
+ }
340
+ async function fetchJson(opts) {
341
+ const { url, method = "GET", headers: headers2 = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
342
+ const doFetch = opts.fetch ?? ((input, init2) => globalThis.fetch(input, init2));
343
+ const init = {
344
+ method,
345
+ headers: {
346
+ "Content-Type": "application/json",
347
+ ...headers2
348
+ },
349
+ signal: AbortSignal.timeout(timeoutMs)
350
+ };
351
+ if (body !== void 0) {
352
+ init.body = JSON.stringify(body);
353
+ }
354
+ let response;
355
+ try {
356
+ response = await doFetch(url, init);
357
+ } catch (cause) {
358
+ const msg = cause instanceof Error ? cause.message : String(cause);
359
+ throw new HttpError(0, msg, url);
360
+ }
361
+ if (!response.ok) {
362
+ throw await httpErrorFromResponse(response, url);
363
+ }
364
+ let text;
365
+ try {
366
+ text = await response.text();
367
+ } catch (cause) {
368
+ const msg = cause instanceof Error ? cause.message : String(cause);
369
+ throw new HttpError(0, `Failed to read response body: ${msg}`, url);
370
+ }
371
+ if (text.trim() === "") return void 0;
372
+ try {
373
+ return JSON.parse(text);
374
+ } catch {
375
+ throw new HttpError(
376
+ response.status,
377
+ `Invalid JSON response: ${text.slice(0, SNIPPET_MAX)}`,
378
+ url
379
+ );
380
+ }
381
+ }
382
+
383
+ // src/core/http-client.ts
384
+ async function httpJson(opts) {
385
+ return fetchJson(opts);
386
+ }
387
+ async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS, headers2) {
388
+ fs4.mkdirSync(path4.dirname(dest), { recursive: true });
389
+ let response;
390
+ try {
391
+ response = await globalThis.fetch(url, {
392
+ signal: AbortSignal.timeout(timeoutMs),
393
+ ...headers2 ? { headers: headers2 } : {}
394
+ });
395
+ } catch (cause) {
396
+ const msg = cause instanceof Error ? cause.message : String(cause);
397
+ throw new HttpError(0, msg, url);
398
+ }
399
+ if (!response.ok) {
400
+ throw new HttpError(response.status, "(download failed)", url);
401
+ }
402
+ if (!response.body) {
403
+ throw new HttpError(0, "Response body is null", url);
404
+ }
405
+ const fileStream = fs4.createWriteStream(dest);
406
+ try {
407
+ await pipeline(response.body, fileStream);
408
+ } catch (err2) {
409
+ if (!fileStream.closed) await once(fileStream, "close");
410
+ throw err2;
411
+ }
412
+ }
413
+ async function fetchBytes(urlOrDataUri) {
414
+ if (urlOrDataUri.startsWith("data:")) {
415
+ const commaIdx = urlOrDataUri.indexOf(",");
416
+ if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
417
+ const encoded = urlOrDataUri.slice(commaIdx + 1);
418
+ return Buffer.from(encoded, "base64");
419
+ }
420
+ let response;
421
+ try {
422
+ response = await globalThis.fetch(urlOrDataUri, {
423
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
424
+ });
425
+ } catch (cause) {
426
+ const msg = cause instanceof Error ? cause.message : String(cause);
427
+ throw new HttpError(0, msg, urlOrDataUri);
428
+ }
429
+ if (!response.ok) {
430
+ throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
431
+ }
432
+ return Buffer.from(await response.arrayBuffer());
433
+ }
379
434
 
380
435
  // src/providers/byteplus/models.ts
381
436
  var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
@@ -455,7 +510,6 @@ function createBytePlusClient() {
455
510
  }
456
511
 
457
512
  // src/providers/byteplus/generators/three-d.ts
458
- init_http_client();
459
513
  function buildThreeDTaskBody(opts) {
460
514
  const promptText = (opts.prompt ?? "").trim();
461
515
  const flagPart = opts.flags?.trim() ?? "";
@@ -763,7 +817,6 @@ function finalizeGeneratedImages(files, opts) {
763
817
  }
764
818
 
765
819
  // src/providers/byteplus/generators/image.ts
766
- init_http_client();
767
820
  import fs7 from "fs";
768
821
  import path7 from "path";
769
822
  var ASPECT_TO_SIZE = {
@@ -927,7 +980,6 @@ function collect2(value, prev) {
927
980
  import path9 from "path";
928
981
 
929
982
  // src/core/video-thumb.ts
930
- init_http_client();
931
983
  import fs8 from "fs";
932
984
  import path8 from "path";
933
985
  var THUMB_KEYS = /* @__PURE__ */ new Set([
@@ -1012,7 +1064,6 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
1012
1064
  }
1013
1065
 
1014
1066
  // src/providers/byteplus/generators/video.ts
1015
- init_http_client();
1016
1067
  function buildFlagSuffix(opts) {
1017
1068
  const parts = [];
1018
1069
  if (opts.resolution) parts.push(`--rs ${opts.resolution}`);
@@ -1823,7 +1874,6 @@ function registerCloudflareCommands(program2) {
1823
1874
  import fs10 from "fs";
1824
1875
  import path14 from "path";
1825
1876
  init_errors();
1826
- init_http_client();
1827
1877
 
1828
1878
  // src/providers/elevenlabs/models.ts
1829
1879
  var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
@@ -1932,19 +1982,19 @@ async function apiPostBinary(endpoint2, body, apiKey, opts = {}) {
1932
1982
  if (opts.query) {
1933
1983
  for (const [k, v] of Object.entries(opts.query)) url.searchParams.set(k, v);
1934
1984
  }
1935
- const headers = authHeaders(apiKey);
1985
+ const headers2 = authHeaders(apiKey);
1936
1986
  let init;
1937
1987
  if (body instanceof FormData) {
1938
1988
  init = {
1939
1989
  method: "POST",
1940
- headers,
1990
+ headers: headers2,
1941
1991
  body,
1942
1992
  signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
1943
1993
  };
1944
1994
  } else {
1945
1995
  init = {
1946
1996
  method: "POST",
1947
- headers: { ...headers, "Content-Type": "application/json" },
1997
+ headers: { ...headers2, "Content-Type": "application/json" },
1948
1998
  body: JSON.stringify(body),
1949
1999
  signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
1950
2000
  };
@@ -2113,7 +2163,6 @@ function registerCloneCommand(parent) {
2113
2163
 
2114
2164
  // src/providers/elevenlabs/commands/dub.ts
2115
2165
  init_errors();
2116
- init_http_client();
2117
2166
  import fs12 from "fs";
2118
2167
  import path16 from "path";
2119
2168
  function registerDubCommand(parent) {
@@ -2582,7 +2631,7 @@ function registerElevenLabsCommands(program2) {
2582
2631
  }
2583
2632
 
2584
2633
  // src/providers/fal/commands/image.ts
2585
- import fs16 from "fs";
2634
+ import fs17 from "fs";
2586
2635
  import path20 from "path";
2587
2636
 
2588
2637
  // src/providers/fal/models.ts
@@ -2597,15 +2646,43 @@ var FAL_DEFAULTS = {
2597
2646
  };
2598
2647
 
2599
2648
  // src/providers/fal/run-helpers.ts
2649
+ import fs16 from "fs";
2650
+ import path19 from "path";
2651
+
2652
+ // src/core/json-input.ts
2600
2653
  init_errors();
2601
- init_http_client();
2602
2654
  import fs15 from "fs";
2603
- import path19 from "path";
2655
+ function parseJsonInput(raw, flag = "--input") {
2656
+ let text;
2657
+ if (raw.startsWith("@")) {
2658
+ try {
2659
+ text = fs15.readFileSync(raw.slice(1), "utf8");
2660
+ } catch (e) {
2661
+ const msg = e instanceof Error ? e.message : String(e);
2662
+ throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
2663
+ }
2664
+ } else {
2665
+ text = raw;
2666
+ }
2667
+ try {
2668
+ const parsed = JSON.parse(text);
2669
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2670
+ throw new Error("input must be a JSON object");
2671
+ }
2672
+ return parsed;
2673
+ } catch (e) {
2674
+ const msg = e instanceof Error ? e.message : String(e);
2675
+ throw new ValidationError(`Invalid ${flag} JSON: ${msg}`);
2676
+ }
2677
+ }
2678
+
2679
+ // src/providers/fal/run-helpers.ts
2680
+ init_redact();
2604
2681
 
2605
2682
  // src/providers/fal/client.ts
2606
2683
  init_errors();
2607
2684
  init_errors();
2608
- init_http_client();
2685
+ init_redact();
2609
2686
  function requireFalKey() {
2610
2687
  const key = resolveKey("FAL_KEY");
2611
2688
  if (!key) {
@@ -2631,7 +2708,7 @@ var FalClient = class {
2631
2708
  }
2632
2709
  /** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
2633
2710
  async requestAbsolute(url, opts = {}) {
2634
- opts.logger?.debug(`${opts.method ?? "GET"} ${url}`);
2711
+ opts.logger?.debug(`${opts.method ?? "GET"} ${redactUrl(url)}`);
2635
2712
  return httpJson({
2636
2713
  url,
2637
2714
  method: opts.method ?? "GET",
@@ -2677,17 +2754,7 @@ function resultPath(model, requestId) {
2677
2754
 
2678
2755
  // src/providers/fal/run-helpers.ts
2679
2756
  function parseInputArg(raw) {
2680
- const text = raw.startsWith("@") ? fs15.readFileSync(raw.slice(1), "utf8") : raw;
2681
- try {
2682
- const parsed = JSON.parse(text);
2683
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2684
- throw new Error("input must be a JSON object");
2685
- }
2686
- return parsed;
2687
- } catch (e) {
2688
- const msg = e instanceof Error ? e.message : String(e);
2689
- throw new ValidationError(`Invalid --input JSON: ${msg}`);
2690
- }
2757
+ return parseJsonInput(raw);
2691
2758
  }
2692
2759
  function isTransientError(e) {
2693
2760
  if (e instanceof HttpError) return e.status === 0 || e.status >= 500;
@@ -2818,8 +2885,8 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
2818
2885
  saved.push(filepath);
2819
2886
  logger.success(`Saved ${filepath}`);
2820
2887
  } catch (e) {
2821
- fs15.rmSync(filepath, { force: true });
2822
- logger.warn(`Download failed for ${url}: ${e instanceof Error ? e.message : e}`);
2888
+ fs16.rmSync(filepath, { force: true });
2889
+ logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
2823
2890
  }
2824
2891
  }
2825
2892
  return saved;
@@ -2864,8 +2931,8 @@ function registerFalImageCommand(parent) {
2864
2931
  }
2865
2932
  if (opts.output && saved[0]) {
2866
2933
  const dest = path20.resolve(opts.output);
2867
- fs16.mkdirSync(path20.dirname(dest), { recursive: true });
2868
- fs16.copyFileSync(saved[0], dest);
2934
+ fs17.mkdirSync(path20.dirname(dest), { recursive: true });
2935
+ fs17.copyFileSync(saved[0], dest);
2869
2936
  logger.success(`Copied to ${dest}`);
2870
2937
  }
2871
2938
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -3020,13 +3087,12 @@ function registerFalCommands(program2) {
3020
3087
 
3021
3088
  // src/providers/gemini/commands/analyze.ts
3022
3089
  init_errors();
3023
- import fs19 from "fs";
3090
+ import fs20 from "fs";
3024
3091
 
3025
3092
  // src/providers/gemini/client.ts
3026
- import fs17 from "fs";
3093
+ import fs18 from "fs";
3027
3094
  import path22 from "path";
3028
3095
  init_errors();
3029
- init_http_client();
3030
3096
 
3031
3097
  // src/providers/gemini/task-resolver.ts
3032
3098
  import path21 from "path";
@@ -3096,7 +3162,7 @@ async function uploadFile(filePath, opts = {}) {
3096
3162
  const apiKey = requireApiKey();
3097
3163
  const { timeoutMs = 3e5, logger } = opts;
3098
3164
  const mimeType = getMimeType(filePath);
3099
- const fileBytes = fs17.readFileSync(filePath);
3165
+ const fileBytes = fs18.readFileSync(filePath);
3100
3166
  const displayName = path22.basename(filePath);
3101
3167
  const boundary = `multix_${Date.now()}`;
3102
3168
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
@@ -3213,12 +3279,12 @@ function extractImages(resp) {
3213
3279
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
3214
3280
  }
3215
3281
  function inlinePart(filePath) {
3216
- const data = fs17.readFileSync(filePath).toString("base64");
3282
+ const data = fs18.readFileSync(filePath).toString("base64");
3217
3283
  const mimeType = getMimeType(filePath);
3218
3284
  return { inlineData: { mimeType, data } };
3219
3285
  }
3220
3286
  function shouldUseFileApi(filePath) {
3221
- const stat = fs17.statSync(filePath);
3287
+ const stat = fs18.statSync(filePath);
3222
3288
  return stat.size > 20 * 1024 * 1024;
3223
3289
  }
3224
3290
  function sleep2(ms) {
@@ -3272,13 +3338,13 @@ function getDefaultModel(task) {
3272
3338
  }
3273
3339
 
3274
3340
  // src/providers/gemini/result-formatter.ts
3275
- import fs18 from "fs";
3341
+ import fs19 from "fs";
3276
3342
  import path23 from "path";
3277
3343
  function formatResults(results, format, outputPath) {
3278
3344
  const content = renderFormat(results, format);
3279
3345
  if (outputPath) {
3280
- fs18.mkdirSync(path23.dirname(path23.resolve(outputPath)), { recursive: true });
3281
- fs18.writeFileSync(outputPath, content, "utf8");
3346
+ fs19.mkdirSync(path23.dirname(path23.resolve(outputPath)), { recursive: true });
3347
+ fs19.writeFileSync(outputPath, content, "utf8");
3282
3348
  } else {
3283
3349
  process.stdout.write(content);
3284
3350
  }
@@ -3345,7 +3411,7 @@ function registerAnalyzeCommand(parent) {
3345
3411
  const logger = createLogger({ verbose: opts.verbose ?? false });
3346
3412
  const model = opts.model ?? getDefaultModel("analyze");
3347
3413
  for (const f of opts.files) {
3348
- if (!fs19.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3414
+ if (!fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3349
3415
  }
3350
3416
  const results = [];
3351
3417
  for (let i = 0; i < opts.files.length; i++) {
@@ -3391,14 +3457,14 @@ function registerAnalyzeCommand(parent) {
3391
3457
 
3392
3458
  // src/providers/gemini/commands/extract.ts
3393
3459
  init_errors();
3394
- import fs20 from "fs";
3460
+ import fs21 from "fs";
3395
3461
  function registerExtractCommand(parent) {
3396
3462
  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(
3397
3463
  async (opts) => {
3398
3464
  const logger = createLogger({ verbose: opts.verbose ?? false });
3399
3465
  const model = opts.model ?? getDefaultModel("extract");
3400
3466
  for (const f of opts.files) {
3401
- if (!fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3467
+ if (!fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3402
3468
  }
3403
3469
  const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
3404
3470
  const results = [];
@@ -3440,7 +3506,7 @@ function registerExtractCommand(parent) {
3440
3506
  init_errors();
3441
3507
 
3442
3508
  // src/providers/gemini/generators/speech.ts
3443
- import fs21 from "fs";
3509
+ import fs22 from "fs";
3444
3510
  import path24 from "path";
3445
3511
 
3446
3512
  // src/providers/gemini/voices.ts
@@ -3605,11 +3671,11 @@ function extractWavData(wav) {
3605
3671
  function saveSpeech(outBytes, outputFormat, output, logger) {
3606
3672
  const outDir = getOutputDir();
3607
3673
  const dest = path24.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
3608
- fs21.writeFileSync(dest, outBytes);
3674
+ fs22.writeFileSync(dest, outBytes);
3609
3675
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
3610
3676
  if (output) {
3611
- fs21.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3612
- fs21.copyFileSync(dest, output);
3677
+ fs22.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
3678
+ fs22.copyFileSync(dest, output);
3613
3679
  logger?.success(`Copied to: ${output}`);
3614
3680
  }
3615
3681
  return dest;
@@ -3721,11 +3787,179 @@ function collectSpeakers(value, prev) {
3721
3787
  }
3722
3788
 
3723
3789
  // src/providers/gemini/commands/generate-video.ts
3724
- import fs22 from "fs";
3790
+ import fs24 from "fs";
3725
3791
  import path25 from "path";
3726
3792
  init_errors();
3727
- init_http_client();
3728
- var BASE2 = "https://generativelanguage.googleapis.com";
3793
+
3794
+ // src/providers/gemini/veo.ts
3795
+ import fs23 from "fs";
3796
+
3797
+ // src/core/async-poll.ts
3798
+ init_errors();
3799
+ var PollTimeoutError2 = class extends MultixError {
3800
+ constructor(message, lastStatus) {
3801
+ super(message, "POLL_TIMEOUT");
3802
+ this.lastStatus = lastStatus;
3803
+ this.name = "PollTimeoutError";
3804
+ }
3805
+ lastStatus;
3806
+ };
3807
+ function isTransientHttpError(error) {
3808
+ if (!(error instanceof HttpError)) return false;
3809
+ return error.status === 0 || error.status === 429 || error.status >= 500;
3810
+ }
3811
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3812
+ async function pollUntil2(opts) {
3813
+ const now = opts.now ?? Date.now;
3814
+ const sleep5 = opts.sleep ?? defaultSleep;
3815
+ const random = opts.random ?? Math.random;
3816
+ const isTransient = opts.isTransientError ?? isTransientHttpError;
3817
+ const maxInterval = opts.maxIntervalMs ?? 15e3;
3818
+ const minInterval = opts.minIntervalMs ?? 500;
3819
+ const factor = opts.backoffFactor ?? 1.5;
3820
+ const jitter = opts.jitter ?? true;
3821
+ const deadline = now() + opts.timeoutMs;
3822
+ let interval = opts.intervalMs ?? 3e3;
3823
+ let attempt = 0;
3824
+ let last;
3825
+ let lastError;
3826
+ for (; ; ) {
3827
+ attempt++;
3828
+ let retryAfterMs;
3829
+ try {
3830
+ last = await opts.fetchStatus({ remainingMs: Math.max(1, deadline - now()), attempt });
3831
+ lastError = void 0;
3832
+ opts.onPoll?.(last, attempt);
3833
+ if (opts.isTerminal(last)) return last;
3834
+ } catch (error) {
3835
+ if (!isTransient(error)) throw error;
3836
+ lastError = error;
3837
+ if (error instanceof HttpError) retryAfterMs = error.retryAfterMs;
3838
+ }
3839
+ const remaining = deadline - now();
3840
+ if (remaining <= 0) {
3841
+ const hint = opts.resumeHint ? ` ${opts.resumeHint}` : "";
3842
+ const cause = lastError instanceof Error ? ` Last error: ${lastError.message}.` : "";
3843
+ throw new PollTimeoutError2(
3844
+ `Timed out after ${opts.timeoutMs}ms waiting for job.${cause}${hint}`,
3845
+ last
3846
+ );
3847
+ }
3848
+ let delay = retryAfterMs ?? interval;
3849
+ if (jitter && retryAfterMs === void 0) delay *= 0.8 + random() * 0.4;
3850
+ await sleep5(Math.min(Math.max(delay, minInterval), remaining));
3851
+ interval = Math.min(interval * factor, maxInterval);
3852
+ }
3853
+ }
3854
+
3855
+ // src/providers/gemini/veo.ts
3856
+ init_errors();
3857
+ var API_HOST = "generativelanguage.googleapis.com";
3858
+ var BASE2 = `https://${API_HOST}/v1beta`;
3859
+ var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
3860
+ var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
3861
+ function veoImageFromFile(imgPath) {
3862
+ if (!fs23.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
3863
+ const detected = getMimeType(imgPath);
3864
+ const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
3865
+ if (!VEO_IMAGE_TYPES.has(mimeType)) {
3866
+ throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
3867
+ }
3868
+ return { bytesBase64Encoded: fs23.readFileSync(imgPath).toString("base64"), mimeType };
3869
+ }
3870
+ function veoFramesFromPaths(paths = []) {
3871
+ if (paths.length > 2) {
3872
+ throw new ValidationError(
3873
+ "--reference-images accepts at most two images (opening frame, closing frame)"
3874
+ );
3875
+ }
3876
+ const [first, last] = paths;
3877
+ return {
3878
+ image: first ? veoImageFromFile(first) : void 0,
3879
+ lastFrame: last ? veoImageFromFile(last) : void 0
3880
+ };
3881
+ }
3882
+ function modelPath(model) {
3883
+ return model.replace(/^models\//, "");
3884
+ }
3885
+ function buildVeoRequest(opts) {
3886
+ const instance = { prompt: opts.prompt };
3887
+ if (opts.image) instance.image = opts.image;
3888
+ if (opts.lastFrame) instance.lastFrame = opts.lastFrame;
3889
+ return {
3890
+ instances: [instance],
3891
+ parameters: { aspectRatio: opts.aspectRatio, resolution: opts.resolution }
3892
+ };
3893
+ }
3894
+ function extractVeoVideoUri(op) {
3895
+ const r = op.response ?? {};
3896
+ return r.generateVideoResponse?.generatedSamples?.[0]?.video?.uri ?? r.generatedSamples?.[0]?.video?.uri ?? r.generatedVideos?.[0]?.video?.uri;
3897
+ }
3898
+ function headers(apiKey) {
3899
+ return { "x-goog-api-key": apiKey };
3900
+ }
3901
+ async function submitVeo(apiKey, model, body) {
3902
+ try {
3903
+ return await httpJson({
3904
+ url: `${BASE2}/models/${modelPath(model)}:predictLongRunning`,
3905
+ method: "POST",
3906
+ headers: headers(apiKey),
3907
+ body
3908
+ });
3909
+ } catch (e) {
3910
+ const msg = e instanceof Error ? e.message : String(e);
3911
+ throw new ProviderError(`Veo submit failed: ${msg}`, "gemini");
3912
+ }
3913
+ }
3914
+ async function waitForVeo(apiKey, operation, logger, opts = {}) {
3915
+ if (operation.done) return checkVeoResult(operation);
3916
+ const finalOp = await pollUntil2({
3917
+ fetchStatus: ({ remainingMs }) => httpJson({
3918
+ url: `${BASE2}/${operation.name}`,
3919
+ headers: headers(apiKey),
3920
+ timeoutMs: Math.min(remainingMs, 6e4)
3921
+ }),
3922
+ isTerminal: (op) => op.done === true,
3923
+ timeoutMs: opts.timeoutMs ?? VEO_TIMEOUT_MS,
3924
+ intervalMs: opts.intervalMs ?? 1e4,
3925
+ maxIntervalMs: 2e4,
3926
+ sleep: opts.sleep,
3927
+ onPoll: (op, attempt) => {
3928
+ if (!op.done) logger.debug(`Still generating... (poll ${attempt})`);
3929
+ },
3930
+ resumeHint: `Operation (still running server-side): ${operation.name}`
3931
+ });
3932
+ return checkVeoResult(finalOp);
3933
+ }
3934
+ function checkVeoResult(op) {
3935
+ if (op.error) throw new ProviderError(`Veo error: ${JSON.stringify(op.error)}`, "gemini");
3936
+ return op;
3937
+ }
3938
+ async function downloadVeoVideo(apiKey, uri, dest) {
3939
+ let host = "";
3940
+ try {
3941
+ host = new URL(uri).hostname;
3942
+ } catch {
3943
+ throw new ProviderError("Veo returned an invalid video URI", "gemini");
3944
+ }
3945
+ await downloadFile(uri, dest, 3e5, host === API_HOST ? headers(apiKey) : void 0);
3946
+ }
3947
+ async function runVeo(apiKey, model, request, dest, logger) {
3948
+ const operation = await submitVeo(apiKey, model, buildVeoRequest(request));
3949
+ logger.debug(`Operation: ${operation.name}`);
3950
+ const finalOp = await waitForVeo(apiKey, operation, logger);
3951
+ const uri = extractVeoVideoUri(finalOp);
3952
+ if (!uri) {
3953
+ throw new ProviderError(
3954
+ `Unexpected Veo response structure: ${JSON.stringify(finalOp.response)}`,
3955
+ "gemini"
3956
+ );
3957
+ }
3958
+ await downloadVeoVideo(apiKey, uri, dest);
3959
+ return finalOp;
3960
+ }
3961
+
3962
+ // src/providers/gemini/commands/generate-video.ts
3729
3963
  function registerGenerateVideoCommand(parent) {
3730
3964
  parent.command("generate-video").description("Generate video with Veo via Gemini API [EXPERIMENTAL]").requiredOption("--prompt <text>", "Video generation prompt").option("--model <id>", "Veo model to use").option("--resolution <res>", "Video resolution: 720p|1080p", "1080p").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option(
3731
3965
  "--reference-images <paths...>",
@@ -3749,72 +3983,24 @@ function registerGenerateVideoCommand(parent) {
3749
3983
  }
3750
3984
  logger.debug(`Submitting video generation with ${model}...`);
3751
3985
  logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
3752
- const config = {
3753
- aspectRatio: opts.aspectRatio,
3754
- resolution: opts.resolution
3755
- };
3756
- const contentParts = [{ text: opts.prompt }];
3757
- if (opts.referenceImages?.length) {
3758
- for (const imgPath of opts.referenceImages.slice(0, 2)) {
3759
- if (!fs22.existsSync(imgPath))
3760
- throw new ValidationError(`Reference image not found: ${imgPath}`);
3761
- const data = fs22.readFileSync(imgPath).toString("base64");
3762
- const ext = path25.extname(imgPath).toLowerCase();
3763
- const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
3764
- contentParts.push({ inlineData: { mimeType, data } });
3765
- }
3766
- }
3767
- const headers = { "x-goog-api-key": apiKey, "Content-Type": "application/json" };
3768
- let operation;
3769
- try {
3770
- operation = await httpJson({
3771
- url: `${BASE2}/v1beta/models/${model}:generateVideos`,
3772
- method: "POST",
3773
- headers,
3774
- body: {
3775
- contents: [{ parts: contentParts }],
3776
- generationConfig: config
3777
- }
3778
- });
3779
- } catch (e) {
3780
- const msg = e instanceof Error ? e.message : String(e);
3781
- throw new ProviderError(`Video generation submit failed: ${msg}`, "gemini");
3782
- }
3783
- const operationName = operation.name;
3784
- logger.debug(`Operation: ${operationName}`);
3785
- const deadline = Date.now() + 10 * 60 * 1e3;
3786
- let done = false;
3787
- let finalOp = operation;
3788
- while (!done && Date.now() < deadline) {
3789
- await sleep3(1e4);
3790
- finalOp = await httpJson({
3791
- url: `${BASE2}/v1beta/${operationName}`,
3792
- headers
3793
- });
3794
- done = finalOp.done === true;
3795
- if (!done)
3796
- logger.debug(
3797
- `Still generating... (${Math.round((deadline - Date.now()) / 1e3)}s left)`
3798
- );
3799
- }
3800
- if (!done) throw new ProviderError("Video generation timed out after 10 minutes", "gemini");
3801
- if (finalOp.error)
3802
- throw new ProviderError(`Veo error: ${JSON.stringify(finalOp.error)}`, "gemini");
3803
- const videoUri = finalOp.response?.generatedSamples?.[0]?.video?.uri ?? finalOp.response?.generatedVideos?.[0]?.video?.uri;
3804
- if (!videoUri) {
3805
- throw new ProviderError(
3806
- `Unexpected Veo response structure: ${JSON.stringify(finalOp.response)}`,
3807
- "gemini"
3808
- );
3809
- }
3810
- const outDir = getOutputDir();
3811
- const dest = path25.join(outDir, `veo_generated_${Date.now()}.mp4`);
3812
- const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
3813
- await downloadFile2(videoUri, dest);
3986
+ const frames = veoFramesFromPaths(opts.referenceImages);
3987
+ const dest = path25.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
3988
+ const finalOp = await runVeo(
3989
+ apiKey,
3990
+ model,
3991
+ {
3992
+ prompt: opts.prompt,
3993
+ ...frames,
3994
+ aspectRatio: opts.aspectRatio,
3995
+ resolution: opts.resolution
3996
+ },
3997
+ dest,
3998
+ logger
3999
+ );
3814
4000
  logger.success(`Saved video: ${dest}`);
3815
4001
  if (opts.output) {
3816
- fs22.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
3817
- fs22.copyFileSync(dest, opts.output);
4002
+ fs24.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
4003
+ fs24.copyFileSync(dest, opts.output);
3818
4004
  logger.success(`Copied to: ${opts.output}`);
3819
4005
  }
3820
4006
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -3827,13 +4013,10 @@ Generated video: ${dest}`);
3827
4013
  }
3828
4014
  );
3829
4015
  }
3830
- function sleep3(ms) {
3831
- return new Promise((r) => setTimeout(r, ms));
3832
- }
3833
4016
 
3834
4017
  // src/providers/gemini/commands/generate.ts
3835
4018
  init_errors();
3836
- import fs23 from "fs";
4019
+ import fs25 from "fs";
3837
4020
  import path26 from "path";
3838
4021
  function registerGenerateCommand(parent) {
3839
4022
  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(
@@ -3877,7 +4060,7 @@ function registerGenerateCommand(parent) {
3877
4060
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
3878
4061
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
3879
4062
  const dest = path26.join(outDir, fname);
3880
- fs23.writeFileSync(dest, Buffer.from(img.data, "base64"));
4063
+ fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
3881
4064
  savedFiles.push(dest);
3882
4065
  logger.success(`Saved: ${dest}`);
3883
4066
  }
@@ -3892,8 +4075,8 @@ function registerGenerateCommand(parent) {
3892
4075
  process.exit(1);
3893
4076
  }
3894
4077
  if (opts.output && savedFiles[0]) {
3895
- fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
3896
- fs23.copyFileSync(savedFiles[0], opts.output);
4078
+ fs25.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
4079
+ fs25.copyFileSync(savedFiles[0], opts.output);
3897
4080
  logger.success(`Copied to: ${opts.output}`);
3898
4081
  }
3899
4082
  const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
@@ -3905,7 +4088,7 @@ Generated ${finalImages.length} image(s):`);
3905
4088
  }
3906
4089
 
3907
4090
  // src/providers/gemini/commands/image-to-image.ts
3908
- import fs24 from "fs";
4091
+ import fs26 from "fs";
3909
4092
  import path27 from "path";
3910
4093
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
3911
4094
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
@@ -3968,13 +4151,13 @@ function registerGeminiImageToImageCommand(parent) {
3968
4151
  if (!img) continue;
3969
4152
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
3970
4153
  const dest = path27.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
3971
- fs24.writeFileSync(dest, Buffer.from(img.data, "base64"));
4154
+ fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
3972
4155
  saved.push(dest);
3973
4156
  logger.success(`Saved: ${dest}`);
3974
4157
  }
3975
4158
  if (opts.output && saved[0]) {
3976
- fs24.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
3977
- fs24.copyFileSync(saved[0], opts.output);
4159
+ fs26.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
4160
+ fs26.copyFileSync(saved[0], opts.output);
3978
4161
  logger.success(`Copied to: ${opts.output}`);
3979
4162
  }
3980
4163
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4010,23 +4193,9 @@ function sniffImageMime(buf, url) {
4010
4193
  }
4011
4194
 
4012
4195
  // src/providers/gemini/commands/image-to-video.ts
4013
- import fs25 from "fs";
4196
+ import fs27 from "fs";
4014
4197
  import path28 from "path";
4015
4198
  init_errors();
4016
- init_http_client();
4017
- var BASE3 = "https://generativelanguage.googleapis.com";
4018
- function imagePart(imgPath) {
4019
- if (!fs25.existsSync(imgPath)) {
4020
- throw new ValidationError(`Image not found: ${imgPath}`);
4021
- }
4022
- const data = fs25.readFileSync(imgPath).toString("base64");
4023
- const ext = path28.extname(imgPath).toLowerCase();
4024
- const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
4025
- return { inlineData: { mimeType, data } };
4026
- }
4027
- function sleep4(ms) {
4028
- return new Promise((r) => setTimeout(r, ms));
4029
- }
4030
4199
  function registerGeminiImageToVideoCommand(parent) {
4031
4200
  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(
4032
4201
  async (imagePath, opts) => {
@@ -4045,60 +4214,27 @@ function registerGeminiImageToVideoCommand(parent) {
4045
4214
  if (!ASPECT_RATIOS.includes(opts.aspectRatio)) {
4046
4215
  throw new ValidationError(`Invalid aspect ratio: ${opts.aspectRatio}`);
4047
4216
  }
4048
- const contentParts = [
4049
- { text: opts.prompt },
4050
- imagePart(imagePath)
4051
- ];
4052
- if (opts.lastFrame) contentParts.push(imagePart(opts.lastFrame));
4053
- const headers = { "x-goog-api-key": apiKey, "Content-Type": "application/json" };
4054
4217
  logger.info(
4055
4218
  "Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
4056
4219
  );
4057
- let operation;
4058
- try {
4059
- operation = await httpJson({
4060
- url: `${BASE3}/v1beta/models/${model}:generateVideos`,
4061
- method: "POST",
4062
- headers,
4063
- body: {
4064
- contents: [{ parts: contentParts }],
4065
- generationConfig: { aspectRatio: opts.aspectRatio, resolution: opts.resolution }
4066
- }
4067
- });
4068
- } catch (e) {
4069
- throw new ProviderError(
4070
- `Submit failed: ${e instanceof Error ? e.message : String(e)}`,
4071
- "gemini"
4072
- );
4073
- }
4074
- const operationName = operation.name;
4075
- logger.debug(`Operation: ${operationName}`);
4076
- const deadline = Date.now() + 10 * 60 * 1e3;
4077
- let finalOp = operation;
4078
- while (!finalOp.done && Date.now() < deadline) {
4079
- await sleep4(1e4);
4080
- finalOp = await httpJson({ url: `${BASE3}/v1beta/${operationName}`, headers });
4081
- if (!finalOp.done) {
4082
- logger.debug(`Still generating... ${Math.round((deadline - Date.now()) / 1e3)}s left`);
4083
- }
4084
- }
4085
- if (!finalOp.done) throw new ProviderError("Veo timed out after 10 minutes", "gemini");
4086
- if (finalOp.error) {
4087
- throw new ProviderError(`Veo error: ${JSON.stringify(finalOp.error)}`, "gemini");
4088
- }
4089
- const videoUri = finalOp.response?.generatedSamples?.[0]?.video?.uri ?? finalOp.response?.generatedVideos?.[0]?.video?.uri;
4090
- if (!videoUri) {
4091
- throw new ProviderError(
4092
- `Unexpected Veo response: ${JSON.stringify(finalOp.response)}`,
4093
- "gemini"
4094
- );
4095
- }
4096
4220
  const dest = path28.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
4097
- await downloadFile(videoUri, dest);
4221
+ const finalOp = await runVeo(
4222
+ apiKey,
4223
+ model,
4224
+ {
4225
+ prompt: opts.prompt,
4226
+ image: veoImageFromFile(imagePath),
4227
+ lastFrame: opts.lastFrame ? veoImageFromFile(opts.lastFrame) : void 0,
4228
+ aspectRatio: opts.aspectRatio,
4229
+ resolution: opts.resolution
4230
+ },
4231
+ dest,
4232
+ logger
4233
+ );
4098
4234
  logger.success(`Saved video: ${dest}`);
4099
4235
  if (opts.output) {
4100
- fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4101
- fs25.copyFileSync(dest, opts.output);
4236
+ fs27.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4237
+ fs27.copyFileSync(dest, opts.output);
4102
4238
  logger.success(`Copied to: ${opts.output}`);
4103
4239
  }
4104
4240
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4114,14 +4250,14 @@ Generated video: ${dest}`);
4114
4250
 
4115
4251
  // src/providers/gemini/commands/transcribe.ts
4116
4252
  init_errors();
4117
- import fs26 from "fs";
4253
+ import fs28 from "fs";
4118
4254
  function registerTranscribeCommand2(parent) {
4119
4255
  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(
4120
4256
  async (opts) => {
4121
4257
  const logger = createLogger({ verbose: opts.verbose ?? false });
4122
4258
  const model = opts.model ?? getDefaultModel("transcribe");
4123
4259
  for (const f of opts.files) {
4124
- if (!fs26.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
4260
+ if (!fs28.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
4125
4261
  }
4126
4262
  const results = [];
4127
4263
  for (let i = 0; i < opts.files.length; i++) {
@@ -4178,13 +4314,11 @@ function registerGeminiCommands(program2) {
4178
4314
  }
4179
4315
 
4180
4316
  // src/providers/leonardo/commands/generate.ts
4181
- init_http_client();
4182
- import fs27 from "fs";
4317
+ import fs29 from "fs";
4183
4318
  import path29 from "path";
4184
4319
 
4185
4320
  // src/providers/leonardo/client.ts
4186
4321
  init_errors();
4187
- init_http_client();
4188
4322
 
4189
4323
  // src/providers/leonardo/models.ts
4190
4324
  var DEFAULT_LEONARDO_BASE_URL = "https://cloud.leonardo.ai/api/rest/v1";
@@ -4391,8 +4525,8 @@ function registerLeonardoGenerateCommand(parent) {
4391
4525
  }
4392
4526
  if (opts.output && saved[0]) {
4393
4527
  const dest = path29.resolve(opts.output);
4394
- fs27.mkdirSync(path29.dirname(dest), { recursive: true });
4395
- fs27.copyFileSync(saved[0], dest);
4528
+ fs29.mkdirSync(path29.dirname(dest), { recursive: true });
4529
+ fs29.copyFileSync(saved[0], dest);
4396
4530
  logger.success(`Copied to ${dest}`);
4397
4531
  }
4398
4532
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4404,8 +4538,7 @@ Generated ${finalImages.length} image(s):`);
4404
4538
  }
4405
4539
 
4406
4540
  // src/providers/leonardo/commands/image-to-image.ts
4407
- init_http_client();
4408
- import fs28 from "fs";
4541
+ import fs30 from "fs";
4409
4542
  import path30 from "path";
4410
4543
  function extFromUrl3(url, fallback = ".png") {
4411
4544
  try {
@@ -4482,8 +4615,8 @@ function registerLeonardoImageToImageCommand(parent) {
4482
4615
  }
4483
4616
  if (opts.output && saved[0]) {
4484
4617
  const dest = path30.resolve(opts.output);
4485
- fs28.mkdirSync(path30.dirname(dest), { recursive: true });
4486
- fs28.copyFileSync(saved[0], dest);
4618
+ fs30.mkdirSync(path30.dirname(dest), { recursive: true });
4619
+ fs30.copyFileSync(saved[0], dest);
4487
4620
  logger.success(`Copied to ${dest}`);
4488
4621
  }
4489
4622
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4495,7 +4628,6 @@ Generated ${finalImages.length} image(s):`);
4495
4628
  }
4496
4629
 
4497
4630
  // src/providers/leonardo/commands/video.ts
4498
- init_http_client();
4499
4631
  import path31 from "path";
4500
4632
  function v2DimsFor(resolution) {
4501
4633
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
@@ -4713,7 +4845,6 @@ function registerLeonardoModelsCommand(parent) {
4713
4845
  }
4714
4846
 
4715
4847
  // src/providers/leonardo/commands/status.ts
4716
- init_http_client();
4717
4848
  import path32 from "path";
4718
4849
  var VIDEO_KEYS = ["motionMP4URL", "url"];
4719
4850
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
@@ -4871,7 +5002,6 @@ init_errors();
4871
5002
 
4872
5003
  // src/providers/minimax/client.ts
4873
5004
  init_errors();
4874
- init_http_client();
4875
5005
  var MINIMAX_BASE_URL = "https://api.minimax.io/v1";
4876
5006
  function requireMinimaxKey() {
4877
5007
  const key = resolveKey("MINIMAX_API_KEY");
@@ -4926,7 +5056,7 @@ async function pollAsyncTask(taskId, taskType, apiKey, opts = {}) {
4926
5056
  if (status === "Failed" || status === "Error") {
4927
5057
  throw new ProviderError(`Task ${taskId} failed: ${JSON.stringify(result)}`, "minimax");
4928
5058
  }
4929
- await sleep5(intervalMs);
5059
+ await sleep3(intervalMs);
4930
5060
  elapsed += intervalMs;
4931
5061
  }
4932
5062
  throw new ProviderError(`Task ${taskId} timed out after ${timeoutMs / 1e3}s`, "minimax");
@@ -4945,13 +5075,12 @@ async function downloadMinimaxFile(fileId, apiKey, dest, logger) {
4945
5075
  await downloadFile(downloadUrl, dest);
4946
5076
  return dest;
4947
5077
  }
4948
- function sleep5(ms) {
5078
+ function sleep3(ms) {
4949
5079
  return new Promise((r) => setTimeout(r, ms));
4950
5080
  }
4951
5081
 
4952
5082
  // src/providers/minimax/generators/music.ts
4953
- init_http_client();
4954
- import fs29 from "fs";
5083
+ import fs31 from "fs";
4955
5084
  import path33 from "path";
4956
5085
  async function generateMinimaxMusic(opts) {
4957
5086
  const {
@@ -5001,10 +5130,10 @@ async function generateMinimaxMusic(opts) {
5001
5130
  try {
5002
5131
  if (audioData.startsWith("http")) {
5003
5132
  const bytes = await fetchBytes(audioData);
5004
- fs29.writeFileSync(dest, bytes);
5133
+ fs31.writeFileSync(dest, bytes);
5005
5134
  } else {
5006
5135
  const audioBytes = Buffer.from(audioData, "hex");
5007
- fs29.writeFileSync(dest, audioBytes);
5136
+ fs31.writeFileSync(dest, audioBytes);
5008
5137
  }
5009
5138
  } catch (e) {
5010
5139
  return {
@@ -5015,8 +5144,8 @@ async function generateMinimaxMusic(opts) {
5015
5144
  const durSec = durationMs / 1e3;
5016
5145
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
5017
5146
  if (output) {
5018
- fs29.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
5019
- fs29.copyFileSync(dest, output);
5147
+ fs31.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
5148
+ fs31.copyFileSync(dest, output);
5020
5149
  }
5021
5150
  return { status: "success", generatedAudio: dest, durationMs, model };
5022
5151
  }
@@ -5090,7 +5219,7 @@ Generated music: ${result.generatedAudio}`);
5090
5219
  init_errors();
5091
5220
 
5092
5221
  // src/providers/minimax/generators/speech.ts
5093
- import fs30 from "fs";
5222
+ import fs32 from "fs";
5094
5223
  import path34 from "path";
5095
5224
  async function generateMinimaxSpeech(opts) {
5096
5225
  const {
@@ -5139,11 +5268,11 @@ async function generateMinimaxSpeech(opts) {
5139
5268
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
5140
5269
  const outDir = getOutputDir();
5141
5270
  const dest = path34.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
5142
- fs30.writeFileSync(dest, audioBytes);
5271
+ fs32.writeFileSync(dest, audioBytes);
5143
5272
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
5144
5273
  if (output) {
5145
- fs30.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5146
- fs30.copyFileSync(dest, output);
5274
+ fs32.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5275
+ fs32.copyFileSync(dest, output);
5147
5276
  }
5148
5277
  return { status: "success", generatedAudio: dest, model };
5149
5278
  }
@@ -5190,7 +5319,7 @@ Generated audio: ${result.generatedAudio}`);
5190
5319
  init_errors();
5191
5320
 
5192
5321
  // src/providers/minimax/generators/video.ts
5193
- import fs31 from "fs";
5322
+ import fs33 from "fs";
5194
5323
  import path35 from "path";
5195
5324
  async function generateMinimaxVideo(opts) {
5196
5325
  const {
@@ -5244,11 +5373,11 @@ async function generateMinimaxVideo(opts) {
5244
5373
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
5245
5374
  }
5246
5375
  const elapsed = (Date.now() - start) / 1e3;
5247
- const stat = fs31.statSync(dest);
5376
+ const stat = fs33.statSync(dest);
5248
5377
  const fileSizeMb = stat.size / (1024 * 1024);
5249
5378
  if (output) {
5250
- fs31.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5251
- fs31.copyFileSync(dest, output);
5379
+ fs33.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5380
+ fs33.copyFileSync(dest, output);
5252
5381
  }
5253
5382
  await maybeDownloadThumb(pollResult, dest, {
5254
5383
  skip: thumb === false,
@@ -5310,8 +5439,7 @@ Generated video: ${result.generatedVideo}`);
5310
5439
 
5311
5440
  // src/providers/minimax/generators/image.ts
5312
5441
  init_errors();
5313
- init_http_client();
5314
- import fs32 from "fs";
5442
+ import fs34 from "fs";
5315
5443
  import path36 from "path";
5316
5444
  async function generateMinimaxImage(opts) {
5317
5445
  const {
@@ -5351,7 +5479,7 @@ async function generateMinimaxImage(opts) {
5351
5479
  const bytes = await fetchBytes(url);
5352
5480
  const fname = `minimax_image_${Date.now()}_${i}.png`;
5353
5481
  const dest = path36.join(outDir, fname);
5354
- fs32.writeFileSync(dest, bytes);
5482
+ fs34.writeFileSync(dest, bytes);
5355
5483
  savedFiles.push(dest);
5356
5484
  logger?.success(`Saved: ${dest}`);
5357
5485
  } catch (e) {
@@ -5362,8 +5490,8 @@ async function generateMinimaxImage(opts) {
5362
5490
  }
5363
5491
  }
5364
5492
  if (output && savedFiles[0]) {
5365
- fs32.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5366
- fs32.copyFileSync(savedFiles[0], output);
5493
+ fs34.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5494
+ fs34.copyFileSync(savedFiles[0], output);
5367
5495
  }
5368
5496
  return { status: "success", generatedImages: savedFiles, model };
5369
5497
  }
@@ -5402,8 +5530,7 @@ Generated ${finalImages.length} image(s):`);
5402
5530
 
5403
5531
  // src/providers/minimax/commands/image-to-image.ts
5404
5532
  init_errors();
5405
- init_http_client();
5406
- import fs33 from "fs";
5533
+ import fs35 from "fs";
5407
5534
  import path37 from "path";
5408
5535
  function registerMinimaxImageToImageCommand(parent) {
5409
5536
  parent.command("image-to-image").alias("i2i").description(
@@ -5460,7 +5587,7 @@ function registerMinimaxImageToImageCommand(parent) {
5460
5587
  try {
5461
5588
  const bytes = await fetchBytes(url);
5462
5589
  const dest = path37.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
5463
- fs33.writeFileSync(dest, bytes);
5590
+ fs35.writeFileSync(dest, bytes);
5464
5591
  saved.push(dest);
5465
5592
  logger.success(`Saved: ${dest}`);
5466
5593
  } catch (e) {
@@ -5471,8 +5598,8 @@ function registerMinimaxImageToImageCommand(parent) {
5471
5598
  }
5472
5599
  }
5473
5600
  if (opts.output && saved[0]) {
5474
- fs33.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
5475
- fs33.copyFileSync(saved[0], opts.output);
5601
+ fs35.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
5602
+ fs35.copyFileSync(saved[0], opts.output);
5476
5603
  logger.success(`Copied to: ${opts.output}`);
5477
5604
  }
5478
5605
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -5606,15 +5733,13 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
5606
5733
  }
5607
5734
 
5608
5735
  // src/providers/openai/openai-audio.ts
5609
- init_http_client();
5610
- import fs35 from "fs";
5736
+ import fs37 from "fs";
5611
5737
  import path39 from "path";
5612
5738
 
5613
5739
  // src/providers/openai/client.ts
5614
- import fs34 from "fs";
5740
+ import fs36 from "fs";
5615
5741
  import path38 from "path";
5616
5742
  init_errors();
5617
- init_http_client();
5618
5743
  var DEFAULT_TIMEOUT_MS3 = 24e4;
5619
5744
  function requireOpenAIKey() {
5620
5745
  const key = resolveKey("OPENAI_API_KEY");
@@ -5680,8 +5805,8 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
5680
5805
  }
5681
5806
  function fileFromPath(filePath) {
5682
5807
  const abs = path38.resolve(filePath);
5683
- if (!fs34.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
5684
- const bytes = fs34.readFileSync(abs);
5808
+ if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
5809
+ const bytes = fs36.readFileSync(abs);
5685
5810
  return new File([new Uint8Array(bytes)], path38.basename(abs));
5686
5811
  }
5687
5812
 
@@ -5741,11 +5866,11 @@ async function generateOpenAISpeech(opts) {
5741
5866
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
5742
5867
  const ext = extFromAudioFormat(opts.outputFormat);
5743
5868
  const dest = path39.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
5744
- fs35.writeFileSync(dest, bytes);
5869
+ fs37.writeFileSync(dest, bytes);
5745
5870
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
5746
5871
  if (opts.output) {
5747
- fs35.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5748
- fs35.copyFileSync(dest, opts.output);
5872
+ fs37.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
5873
+ fs37.copyFileSync(dest, opts.output);
5749
5874
  }
5750
5875
  return dest;
5751
5876
  }
@@ -5768,8 +5893,8 @@ async function transcribeOpenAIAudio(opts) {
5768
5893
  const text = typeof data.text === "string" ? data.text : void 0;
5769
5894
  const ext = opts.format === "text" ? "txt" : "json";
5770
5895
  const dest = opts.output ? path39.resolve(opts.output) : path39.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
5771
- fs35.mkdirSync(path39.dirname(dest), { recursive: true });
5772
- fs35.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
5896
+ fs37.mkdirSync(path39.dirname(dest), { recursive: true });
5897
+ fs37.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
5773
5898
  opts.logger?.success(`Saved transcript: ${dest}`);
5774
5899
  return { path: dest, text };
5775
5900
  }
@@ -5796,7 +5921,7 @@ Generated audio: ${dest}`);
5796
5921
  }
5797
5922
 
5798
5923
  // src/providers/openai/codex-image-driver.ts
5799
- import fs36 from "fs";
5924
+ import fs38 from "fs";
5800
5925
  import os2 from "os";
5801
5926
  import path40 from "path";
5802
5927
  import { execa } from "execa";
@@ -5848,11 +5973,11 @@ async function runCodexImageGeneration(opts) {
5848
5973
  const runner = opts.runner ?? defaultRunner;
5849
5974
  const ext = extFromImageFormat(opts.outputFormat);
5850
5975
  const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
5851
- fs36.mkdirSync(path40.dirname(dest), { recursive: true });
5852
- const tmp = fs36.mkdtempSync(path40.join(os2.tmpdir(), "multix-codex-image-"));
5976
+ fs38.mkdirSync(path40.dirname(dest), { recursive: true });
5977
+ const tmp = fs38.mkdtempSync(path40.join(os2.tmpdir(), "multix-codex-image-"));
5853
5978
  const schema = path40.join(tmp, "schema.json");
5854
5979
  const result = path40.join(tmp, "result.json");
5855
- fs36.writeFileSync(
5980
+ fs38.writeFileSync(
5856
5981
  schema,
5857
5982
  JSON.stringify({
5858
5983
  type: "object",
@@ -5882,7 +6007,7 @@ async function runCodexImageGeneration(opts) {
5882
6007
  if (res.exitCode !== 0) {
5883
6008
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
5884
6009
  }
5885
- if (!fs36.existsSync(dest) || fs36.statSync(dest).size === 0) {
6010
+ if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
5886
6011
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
5887
6012
  }
5888
6013
  return { path: dest };
@@ -5899,8 +6024,7 @@ function buildCodexPrompt(userPrompt, outputPath) {
5899
6024
 
5900
6025
  // src/providers/openai/openai-image.ts
5901
6026
  init_errors();
5902
- init_http_client();
5903
- import fs37 from "fs";
6027
+ import fs39 from "fs";
5904
6028
  import path41 from "path";
5905
6029
  function buildImageGenerationBody(opts) {
5906
6030
  return {
@@ -5935,13 +6059,13 @@ function saveOpenAIImages(opts) {
5935
6059
  const b64 = items[i]?.b64_json;
5936
6060
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
5937
6061
  const dest = path41.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
5938
- fs37.writeFileSync(dest, Buffer.from(b64, "base64"));
6062
+ fs39.writeFileSync(dest, Buffer.from(b64, "base64"));
5939
6063
  saved.push(dest);
5940
6064
  opts.logger?.success(`Saved: ${dest}`);
5941
6065
  }
5942
6066
  if (opts.output && saved[0]) {
5943
- fs37.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
5944
- fs37.copyFileSync(saved[0], opts.output);
6067
+ fs39.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
6068
+ fs39.copyFileSync(saved[0], opts.output);
5945
6069
  opts.logger?.success(`Copied to: ${opts.output}`);
5946
6070
  }
5947
6071
  return saved;
@@ -6139,10 +6263,9 @@ function registerOpenAICommands(program2) {
6139
6263
  }
6140
6264
 
6141
6265
  // src/providers/openrouter/client.ts
6142
- import fs38 from "fs";
6266
+ import fs40 from "fs";
6143
6267
  import path42 from "path";
6144
6268
  init_errors();
6145
- init_http_client();
6146
6269
 
6147
6270
  // src/providers/openrouter/payload.ts
6148
6271
  var IMAGE_ONLY_MODEL_PREFIXES = [
@@ -6155,15 +6278,15 @@ function resolveModalities(model) {
6155
6278
  return IMAGE_ONLY_MODEL_PREFIXES.some((p) => lower.startsWith(p)) ? ["image"] : ["image", "text"];
6156
6279
  }
6157
6280
  function buildOpenRouterHeaders(apiKey) {
6158
- const headers = {
6281
+ const headers2 = {
6159
6282
  Authorization: `Bearer ${apiKey}`,
6160
6283
  "Content-Type": "application/json"
6161
6284
  };
6162
6285
  const referer = resolveKey("OPENROUTER_SITE_URL");
6163
6286
  const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
6164
- if (referer) headers["HTTP-Referer"] = referer;
6165
- if (title) headers["X-Title"] = title;
6166
- return headers;
6287
+ if (referer) headers2["HTTP-Referer"] = referer;
6288
+ if (title) headers2["X-Title"] = title;
6289
+ return headers2;
6167
6290
  }
6168
6291
  function extractImagesFromResponse(data) {
6169
6292
  const out = { urls: [] };
@@ -6306,7 +6429,7 @@ async function generateOpenRouterImage(opts) {
6306
6429
  try {
6307
6430
  const bytes = await fetchBytes(url);
6308
6431
  const dest = path42.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
6309
- fs38.writeFileSync(dest, bytes);
6432
+ fs40.writeFileSync(dest, bytes);
6310
6433
  savedFiles.push(dest);
6311
6434
  logger?.success(`Saved: ${dest}`);
6312
6435
  } catch (e) {
@@ -6317,8 +6440,8 @@ async function generateOpenRouterImage(opts) {
6317
6440
  }
6318
6441
  }
6319
6442
  if (output && savedFiles[0]) {
6320
- fs38.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
6321
- fs38.writeFileSync(output, fs38.readFileSync(savedFiles[0]));
6443
+ fs40.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
6444
+ fs40.writeFileSync(output, fs40.readFileSync(savedFiles[0]));
6322
6445
  }
6323
6446
  return { status: "success", generatedImages: savedFiles, model: usedModel };
6324
6447
  }
@@ -6359,10 +6482,9 @@ Generated ${finalImages.length} image(s):`);
6359
6482
  }
6360
6483
 
6361
6484
  // src/providers/openrouter/commands/image-to-image.ts
6362
- import fs39 from "fs";
6485
+ import fs41 from "fs";
6363
6486
  import path43 from "path";
6364
6487
  init_errors();
6365
- init_http_client();
6366
6488
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
6367
6489
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
6368
6490
  var HARD_LIMIT_BYTES2 = 8 * 1024 * 1024;
@@ -6427,7 +6549,7 @@ function parseStrength(raw) {
6427
6549
  }
6428
6550
  async function runImageToImage(opts) {
6429
6551
  const apiKey = requireOpenRouterKey();
6430
- const headers = buildOpenRouterHeaders(apiKey);
6552
+ const headers2 = buildOpenRouterHeaders(apiKey);
6431
6553
  const payload = buildI2IPayload({
6432
6554
  prompt: opts.prompt,
6433
6555
  model: opts.model,
@@ -6438,7 +6560,7 @@ async function runImageToImage(opts) {
6438
6560
  const data = await httpJson({
6439
6561
  url: OPENROUTER_API_URL,
6440
6562
  method: "POST",
6441
- headers,
6563
+ headers: headers2,
6442
6564
  body: payload,
6443
6565
  timeoutMs: 24e4
6444
6566
  });
@@ -6454,31 +6576,30 @@ async function runImageToImage(opts) {
6454
6576
  if (!url) continue;
6455
6577
  const bytes = await fetchBytes(url);
6456
6578
  const dest = path43.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
6457
- fs39.writeFileSync(dest, bytes);
6579
+ fs41.writeFileSync(dest, bytes);
6458
6580
  saved.push(dest);
6459
6581
  opts.logger.success(`Saved: ${dest}`);
6460
6582
  }
6461
6583
  if (opts.output && saved[0]) {
6462
- fs39.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
6463
- fs39.copyFileSync(saved[0], opts.output);
6584
+ fs41.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
6585
+ fs41.copyFileSync(saved[0], opts.output);
6464
6586
  opts.logger.success(`Copied to: ${opts.output}`);
6465
6587
  }
6466
6588
  return saved;
6467
6589
  }
6468
6590
 
6469
6591
  // src/providers/openrouter/video-client.ts
6470
- init_http_client();
6471
6592
  var OPENROUTER_VIDEOS_URL = "https://openrouter.ai/api/v1/videos";
6472
6593
  function authHeaders5(apiKey) {
6473
- const headers = {
6594
+ const headers2 = {
6474
6595
  Authorization: `Bearer ${apiKey}`,
6475
6596
  "Content-Type": "application/json"
6476
6597
  };
6477
6598
  const referer = resolveKey("OPENROUTER_SITE_URL");
6478
6599
  const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
6479
- if (referer) headers["HTTP-Referer"] = referer;
6480
- if (title) headers["X-Title"] = title;
6481
- return headers;
6600
+ if (referer) headers2["HTTP-Referer"] = referer;
6601
+ if (title) headers2["X-Title"] = title;
6602
+ return headers2;
6482
6603
  }
6483
6604
  async function submitVideoJob(body, apiKey = requireOpenRouterKey()) {
6484
6605
  return httpJson({
@@ -6506,7 +6627,7 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
6506
6627
  }
6507
6628
 
6508
6629
  // src/providers/openrouter/commands/video-status.ts
6509
- import fs40 from "fs";
6630
+ import fs42 from "fs";
6510
6631
  import path44 from "path";
6511
6632
  async function downloadOpenRouterVideo(opts) {
6512
6633
  const { jobId, logger, source } = opts;
@@ -6522,14 +6643,14 @@ async function downloadOpenRouterVideo(opts) {
6522
6643
  logger.error("Empty response body");
6523
6644
  process.exit(1);
6524
6645
  }
6525
- fs40.mkdirSync(path44.dirname(dest), { recursive: true });
6526
- const ws = fs40.createWriteStream(dest);
6646
+ fs42.mkdirSync(path44.dirname(dest), { recursive: true });
6647
+ const ws = fs42.createWriteStream(dest);
6527
6648
  const { pipeline: pipeline2 } = await import("stream/promises");
6528
6649
  await pipeline2(r.body, ws);
6529
6650
  logger.success(`Saved ${dest}`);
6530
6651
  if (opts.output) {
6531
- fs40.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6532
- fs40.copyFileSync(dest, opts.output);
6652
+ fs42.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6653
+ fs42.copyFileSync(dest, opts.output);
6533
6654
  logger.success(`Copied to ${opts.output}`);
6534
6655
  }
6535
6656
  await maybeDownloadThumb(source, dest, {
@@ -6715,12 +6836,12 @@ async function checkBinary(binary, versionFlag = "--version") {
6715
6836
 
6716
6837
  // src/commands/check-helpers/gemini-ping.ts
6717
6838
  init_errors();
6718
- init_http_client();
6719
6839
  var MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
6720
6840
  async function pingGemini(apiKey) {
6721
6841
  try {
6722
6842
  const resp = await httpJson({
6723
- url: `${MODELS_URL}?key=${encodeURIComponent(apiKey)}`,
6843
+ url: MODELS_URL,
6844
+ headers: { "x-goog-api-key": apiKey },
6724
6845
  timeoutMs: 1e4
6725
6846
  });
6726
6847
  const modelCount = Array.isArray(resp.models) ? resp.models.length : 0;
@@ -6947,7 +7068,7 @@ function registerCheckCommand(program2) {
6947
7068
 
6948
7069
  // src/commands/doc/convert.ts
6949
7070
  init_errors();
6950
- import fs41 from "fs";
7071
+ import fs43 from "fs";
6951
7072
  import path45 from "path";
6952
7073
 
6953
7074
  // src/commands/doc/default-prompt.ts
@@ -6969,7 +7090,7 @@ async function convertDocuments(opts) {
6969
7090
  const model = opts.model ?? DOC_MODEL_DEFAULT;
6970
7091
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
6971
7092
  for (const f of inputs) {
6972
- if (!fs41.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
7093
+ if (!fs43.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
6973
7094
  }
6974
7095
  let outputPath;
6975
7096
  if (output) {
@@ -6980,7 +7101,7 @@ async function convertDocuments(opts) {
6980
7101
  } else {
6981
7102
  outputPath = path45.join(getOutputDir(), "document-extraction.md");
6982
7103
  }
6983
- fs41.mkdirSync(path45.dirname(outputPath), { recursive: true });
7104
+ fs43.mkdirSync(path45.dirname(outputPath), { recursive: true });
6984
7105
  const results = [];
6985
7106
  for (let i = 0; i < inputs.length; i++) {
6986
7107
  const filePath = inputs[i];
@@ -7012,7 +7133,7 @@ async function convertDocuments(opts) {
7012
7133
  if (attempt < 2) {
7013
7134
  const wait = 2 ** attempt * 1e3;
7014
7135
  logger?.debug(`Retry ${attempt + 1} after ${wait / 1e3}s: ${errorMsg}`);
7015
- await sleep6(wait);
7136
+ await sleep4(wait);
7016
7137
  }
7017
7138
  }
7018
7139
  }
@@ -7044,7 +7165,7 @@ async function convertDocuments(opts) {
7044
7165
  }
7045
7166
  lines.push("\n---\n");
7046
7167
  }
7047
- fs41.writeFileSync(outputPath, lines.join("\n"), "utf8");
7168
+ fs43.writeFileSync(outputPath, lines.join("\n"), "utf8");
7048
7169
  const succeeded = results.filter((r) => r.status === "success").length;
7049
7170
  console.log(`
7050
7171
  ${"=".repeat(50)}`);
@@ -7060,7 +7181,7 @@ ${"=".repeat(50)}`);
7060
7181
  const failed = results.filter((r) => r.status === "error").length;
7061
7182
  if (failed > 0) process.exit(1);
7062
7183
  }
7063
- function sleep6(ms) {
7184
+ function sleep4(ms) {
7064
7185
  return new Promise((r) => setTimeout(r, ms));
7065
7186
  }
7066
7187
 
@@ -7092,11 +7213,11 @@ function registerDocCommands(program2) {
7092
7213
 
7093
7214
  // src/commands/media/index.ts
7094
7215
  init_errors();
7095
- import fs44 from "fs";
7216
+ import fs46 from "fs";
7096
7217
  import path48 from "path";
7097
7218
 
7098
7219
  // src/commands/media/batch.ts
7099
- import fs42 from "fs";
7220
+ import fs44 from "fs";
7100
7221
  import path46 from "path";
7101
7222
 
7102
7223
  // src/commands/media/detect-type.ts
@@ -7293,8 +7414,8 @@ async function batchOptimize(opts) {
7293
7414
  verbose,
7294
7415
  logger
7295
7416
  } = opts;
7296
- fs42.mkdirSync(outputDir, { recursive: true });
7297
- const entries = fs42.readdirSync(inputDir);
7417
+ fs44.mkdirSync(outputDir, { recursive: true });
7418
+ const entries = fs44.readdirSync(inputDir);
7298
7419
  const mediaFiles = entries.filter((f) => {
7299
7420
  const ext = path46.extname(f).toLowerCase();
7300
7421
  return ALL_EXTS.has(ext);
@@ -7346,7 +7467,7 @@ async function batchOptimize(opts) {
7346
7467
 
7347
7468
  // src/commands/media/split-video.ts
7348
7469
  init_errors();
7349
- import fs43 from "fs";
7470
+ import fs45 from "fs";
7350
7471
  import path47 from "path";
7351
7472
  async function splitVideo(opts) {
7352
7473
  await assertFfmpeg();
@@ -7358,7 +7479,7 @@ async function splitVideo(opts) {
7358
7479
  logger?.info("Video is short enough \u2014 no splitting needed");
7359
7480
  return [input];
7360
7481
  }
7361
- fs43.mkdirSync(outputDir, { recursive: true });
7482
+ fs45.mkdirSync(outputDir, { recursive: true });
7362
7483
  const ext = path47.extname(input) || ".mp4";
7363
7484
  const baseName = path47.basename(input, ext);
7364
7485
  const outputPattern = path47.join(outputDir, `${baseName}_chunk_%03d${ext}`);
@@ -7381,7 +7502,7 @@ async function splitVideo(opts) {
7381
7502
  const files = [];
7382
7503
  for (let i = 0; i < numChunks + 1; i++) {
7383
7504
  const fname = path47.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
7384
- if (fs43.existsSync(fname)) files.push(fname);
7505
+ if (fs45.existsSync(fname)) files.push(fname);
7385
7506
  }
7386
7507
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
7387
7508
  return files;
@@ -7395,8 +7516,8 @@ function registerMediaCommands(program2) {
7395
7516
  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(
7396
7517
  async (opts) => {
7397
7518
  const logger = createLogger({ verbose: opts.verbose ?? false });
7398
- if (!fs44.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7399
- fs44.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
7519
+ if (!fs46.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7520
+ fs46.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
7400
7521
  const ext = path48.extname(opts.input).toLowerCase();
7401
7522
  const kind = extToKind(ext);
7402
7523
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
@@ -7437,7 +7558,7 @@ function registerMediaCommands(program2) {
7437
7558
  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(
7438
7559
  async (opts) => {
7439
7560
  const logger = createLogger({ verbose: opts.verbose ?? false });
7440
- if (!fs44.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7561
+ if (!fs46.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7441
7562
  const chunks = await splitVideo({
7442
7563
  input: opts.input,
7443
7564
  outputDir: opts.outputDir,
@@ -7453,7 +7574,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
7453
7574
  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(
7454
7575
  async (opts) => {
7455
7576
  const logger = createLogger({ verbose: opts.verbose ?? false });
7456
- if (!fs44.existsSync(opts.inputDir))
7577
+ if (!fs46.existsSync(opts.inputDir))
7457
7578
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
7458
7579
  const result = await batchOptimize({
7459
7580
  inputDir: opts.inputDir,
@@ -7476,7 +7597,6 @@ Processed: ${result.succeeded}/${result.total} files`);
7476
7597
 
7477
7598
  // src/commands/update.ts
7478
7599
  init_errors();
7479
- init_http_client();
7480
7600
  import { createRequire } from "module";
7481
7601
  import { execa as execa5 } from "execa";
7482
7602
 
@@ -7640,7 +7760,7 @@ function registerCommands(program2) {
7640
7760
 
7641
7761
  // src/core/index.ts
7642
7762
  init_errors();
7643
- init_http_client();
7763
+ init_redact();
7644
7764
 
7645
7765
  // src/cli.ts
7646
7766
  loadEnv();