@mrgoonie/multix 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -99,12 +99,1923 @@ var init_errors = __esm({
99
99
  import { createRequire as createRequire2 } from "module";
100
100
  import { Command } from "commander";
101
101
 
102
+ // src/providers/atlascloud/commands/audio.ts
103
+ init_errors();
104
+
105
+ // src/providers/atlascloud/model-input.ts
106
+ init_errors();
107
+ var SUBMIT_PATH_RE = /\/model\/generate(Image|Video|Audio)$/;
108
+ function resolveRef(doc, ref) {
109
+ if (typeof ref !== "string" || !ref.startsWith("#/")) return void 0;
110
+ let node = doc;
111
+ for (const part of ref.slice(2).split("/")) {
112
+ node = node?.[part];
113
+ if (node === void 0) return void 0;
114
+ }
115
+ return node;
116
+ }
117
+ function parseModelSpec(model, doc) {
118
+ if (!doc || typeof doc !== "object") return void 0;
119
+ const d = doc;
120
+ let endpoint2;
121
+ let input;
122
+ for (const [p, ops] of Object.entries(d.paths ?? {})) {
123
+ const m = SUBMIT_PATH_RE.exec(p);
124
+ if (!m?.[1] || !ops?.post) continue;
125
+ endpoint2 = m[1].toLowerCase();
126
+ const schema = ops.post.requestBody?.content?.["application/json"]?.schema;
127
+ input = schema?.$ref ? resolveRef(d, schema.$ref) : schema;
128
+ break;
129
+ }
130
+ input ??= d.components?.schemas?.Input;
131
+ if (!input || typeof input.properties !== "object") return void 0;
132
+ return {
133
+ model,
134
+ endpoint: endpoint2,
135
+ properties: input.properties,
136
+ required: Array.isArray(input.required) ? input.required.filter((r) => typeof r === "string") : []
137
+ };
138
+ }
139
+ var FIELD_CANDIDATES = {
140
+ prompt: ["prompt"],
141
+ negativePrompt: ["negative_prompt"],
142
+ seed: ["seed"],
143
+ size: ["size", "image_size"],
144
+ aspectRatio: ["aspect_ratio"],
145
+ resolution: ["resolution"],
146
+ duration: ["duration"],
147
+ numImages: ["num_images"],
148
+ editImages: [
149
+ "images",
150
+ "image",
151
+ "image_urls",
152
+ "reference_images",
153
+ "image_url",
154
+ "reference_image_urls"
155
+ ],
156
+ primaryImage: [
157
+ "image",
158
+ "image_url",
159
+ "images",
160
+ "image_urls",
161
+ "reference_images",
162
+ "reference_image_urls"
163
+ ],
164
+ speechText: ["text", "prompt"],
165
+ audioInput: ["audio_url", "audio"],
166
+ voice: ["voice_id", "voice"],
167
+ audioFormat: ["format", "codec", "output_format"],
168
+ language: ["language"],
169
+ modelFormat: ["format", "output_format", "target_formats"]
170
+ };
171
+ var FALLBACK_FIELD_TYPES = {
172
+ seed: "integer",
173
+ num_images: "integer",
174
+ duration: "number",
175
+ images: "array"
176
+ };
177
+ function coerce(flag, field, prop, value) {
178
+ const type = prop.type;
179
+ if (Array.isArray(value)) {
180
+ if (type === "array") return value;
181
+ if (value.length === 1) return coerceScalar(flag, field, type, value[0]);
182
+ throw new ValidationError(
183
+ `${flag}: field "${field}" of this model accepts a single value, got ${value.length}`
184
+ );
185
+ }
186
+ if (type === "array") return [coerceScalar(flag, field, prop.items?.type, value)];
187
+ return coerceScalar(flag, field, type, value);
188
+ }
189
+ function coerceScalar(flag, field, type, value) {
190
+ if (type === "integer" || type === "number") {
191
+ const n = typeof value === "number" ? value : Number(String(value).trim());
192
+ if (!Number.isFinite(n) || type === "integer" && !Number.isInteger(n)) {
193
+ throw new ValidationError(`${flag}: "${value}" is not a valid ${type} for "${field}"`);
194
+ }
195
+ return n;
196
+ }
197
+ if (type === "boolean") {
198
+ if (typeof value === "boolean") return value;
199
+ const s = String(value).toLowerCase();
200
+ if (s === "true") return true;
201
+ if (s === "false") return false;
202
+ throw new ValidationError(`${flag}: "${value}" is not a boolean for "${field}"`);
203
+ }
204
+ if (type === "string") return String(value);
205
+ return value;
206
+ }
207
+ function checkConstraints(flag, field, prop, v) {
208
+ const values = Array.isArray(v) ? v : [v];
209
+ if (Array.isArray(prop.enum) && prop.enum.length > 0 && !Array.isArray(v)) {
210
+ if (!prop.enum.some((e) => e === v || String(e) === String(v))) {
211
+ throw new ValidationError(
212
+ `${flag}: "${String(v)}" is not supported by this model. Valid ${field}: ${prop.enum.map(String).join(", ")}`
213
+ );
214
+ }
215
+ }
216
+ for (const item of values) {
217
+ if (typeof item !== "number") continue;
218
+ if (prop.minimum !== void 0 && item < prop.minimum) {
219
+ throw new ValidationError(
220
+ `${flag}: ${item} is below the minimum ${prop.minimum} for "${field}"`
221
+ );
222
+ }
223
+ if (prop.maximum !== void 0 && item > prop.maximum) {
224
+ throw new ValidationError(
225
+ `${flag}: ${item} exceeds the maximum ${prop.maximum} for "${field}"`
226
+ );
227
+ }
228
+ }
229
+ if (Array.isArray(v) && prop.maxItems !== void 0 && v.length > prop.maxItems) {
230
+ throw new ValidationError(
231
+ `${flag}: this model accepts at most ${prop.maxItems} item(s) for "${field}", got ${v.length}`
232
+ );
233
+ }
234
+ }
235
+ function pickField(spec, req) {
236
+ const declared = req.candidates.filter((c2) => spec.properties[c2] !== void 0);
237
+ const compatible = declared.filter((c2) => {
238
+ if (spec.properties[c2]?.type !== "boolean") return true;
239
+ const v = req.value;
240
+ return typeof v === "boolean" || typeof v === "string" && /^(true|false)$/i.test(v);
241
+ });
242
+ if (Array.isArray(req.value) && req.value.length > 1) {
243
+ return compatible.find((c2) => spec.properties[c2]?.type === "array") ?? compatible[0];
244
+ }
245
+ return compatible[0];
246
+ }
247
+ function buildModelInput(spec, requests, base = {}) {
248
+ const body = { ...base };
249
+ for (const req of requests) {
250
+ if (req.value === void 0 || Array.isArray(req.value) && req.value.length === 0) continue;
251
+ if (!spec) {
252
+ const field2 = req.candidates[0];
253
+ if (field2) {
254
+ const type = FALLBACK_FIELD_TYPES[field2] ?? "string";
255
+ body[field2] = coerce(req.flag, field2, { type }, req.value);
256
+ }
257
+ continue;
258
+ }
259
+ const field = pickField(spec, req);
260
+ if (!field) {
261
+ throw new ValidationError(
262
+ `Model ${spec.model} does not accept ${req.flag}. Its inputs: ${listInputs(spec)}. Use \`multix atlas run ${spec.model} --input '{...}'\` for model-specific fields.`
263
+ );
264
+ }
265
+ const prop = spec.properties[field];
266
+ const value = coerce(req.flag, field, prop, req.value);
267
+ checkConstraints(req.flag, field, prop, value);
268
+ body[field] = value;
269
+ }
270
+ if (spec) finalizeAgainstSpec(spec, body, requests);
271
+ return body;
272
+ }
273
+ function listInputs(spec) {
274
+ return Object.keys(spec.properties).filter((k) => k !== "model").join(", ");
275
+ }
276
+ var CONTENT_FIELDS = /* @__PURE__ */ new Set(["prompt", "text", "lyrics"]);
277
+ function finalizeAgainstSpec(spec, body, requests = []) {
278
+ const missing = [];
279
+ for (const field of spec.required) {
280
+ if (field === "model" || body[field] !== void 0) continue;
281
+ const def = spec.properties[field]?.default;
282
+ if (def !== void 0 && def !== "" && !CONTENT_FIELDS.has(field)) {
283
+ body[field] = def;
284
+ continue;
285
+ }
286
+ const flag = requests.find((r) => r.candidates.includes(field))?.flag;
287
+ missing.push(flag ? `${field} (pass ${flag})` : field);
288
+ }
289
+ if (missing.length > 0) {
290
+ throw new ValidationError(`Model ${spec.model} requires: ${missing.join(", ")}`);
291
+ }
292
+ }
293
+ function validateRawInput(spec, body) {
294
+ const unknown = [];
295
+ for (const [field, value] of Object.entries(body)) {
296
+ if (field === "model") continue;
297
+ const prop = spec.properties[field];
298
+ if (!prop) {
299
+ unknown.push(field);
300
+ continue;
301
+ }
302
+ checkConstraints(`--input.${field}`, field, prop, value);
303
+ }
304
+ finalizeAgainstSpec(spec, body);
305
+ return unknown;
306
+ }
307
+
308
+ // src/providers/atlascloud/models.ts
309
+ var DEFAULT_ATLAS_BASE_URL = "https://api.atlascloud.ai/api/v1";
310
+ var SUBMIT_PATHS = {
311
+ image: "/model/generateImage",
312
+ video: "/model/generateVideo",
313
+ audio: "/model/generateAudio"
314
+ };
315
+ var ATLAS_MEDIA_KINDS = ["image", "video", "audio"];
316
+ var ATLAS_DEFAULT_MODELS = {
317
+ /** FLUX.1 [schnell], $0.003/image — fastest, cheapest text-to-image. */
318
+ image: "black-forest-labs/flux-schnell",
319
+ /** GPT Image 1 Mini edit — cheapest image edit; takes `images[]`. */
320
+ imageEdit: "openai/gpt-image-1-mini/edit",
321
+ /** Seedance 1.0 Pro Fast — low-cost text-to-video, 2-12s, 480p-1080p. */
322
+ video: "bytedance/seedance-v1-pro-fast/text-to-video",
323
+ /** Matching image-to-video model; takes a single `image`. */
324
+ imageToVideo: "bytedance/seedance-v1-pro-fast/image-to-video",
325
+ /** xAI TTS v1 — cheapest text-to-speech. */
326
+ tts: "xai/tts-v1",
327
+ /** Seed ASR 2.0 — documented speech-to-text example; returns text. */
328
+ stt: "bytedance/seed-asr-2.0",
329
+ /** Hunyuan 3D Rapid — low-cost text-to-3D (GLB by default). */
330
+ textTo3d: "tencent/hunyuan3d-rapid/text-to-3d",
331
+ /** Hunyuan 3D Rapid — low-cost image-to-3D (GLB by default). */
332
+ imageTo3d: "tencent/hunyuan3d-rapid/image-to-3d"
333
+ };
334
+
335
+ // src/providers/atlascloud/commands/env.ts
336
+ import { openAsBlob } from "fs";
337
+ import fs2 from "fs/promises";
338
+ import path2 from "path";
339
+
340
+ // src/core/env-loader.ts
341
+ init_redact();
342
+ import fs from "fs";
343
+ import os from "os";
344
+ import path from "path";
345
+ import { config as dotenvConfig, parse as dotenvParse } from "dotenv";
346
+ var loaded = false;
347
+ var loadedFiles = [];
348
+ var LEGACY_SKILL_DIRS = ["ai-multimodal", "ak-ai-multimodal"];
349
+ function legacyFilesUnder(root) {
350
+ const claude = path.join(root, ".claude");
351
+ return [
352
+ ...LEGACY_SKILL_DIRS.map((dir) => path.join(claude, "skills", dir, ".env")),
353
+ path.join(claude, "skills", ".env"),
354
+ path.join(claude, ".env")
355
+ ];
356
+ }
357
+ function samePath(a, b) {
358
+ const norm2 = (p) => {
359
+ const r = path.resolve(p);
360
+ return process.platform === "win32" ? r.toLowerCase() : r;
361
+ };
362
+ return norm2(a) === norm2(b);
363
+ }
364
+ function legacyClaudeEnvFiles(cwd, home) {
365
+ const files = legacyFilesUnder(cwd);
366
+ if (home && !samePath(home, cwd)) files.push(...legacyFilesUnder(home));
367
+ return files;
368
+ }
369
+ function legacyClaudeEnvEnabled(env = process.env) {
370
+ const value = env.MULTIX_LEGACY_CLAUDE_ENV;
371
+ return value !== void 0 && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
372
+ }
373
+ var LEGACY_ALLOWED_NAME = /^[A-Z][A-Z0-9_]*_(API_KEY(_\d+)?|API_TOKEN|ACCOUNT_ID|GATEWAY_ID)$|^(FAL_KEY|ARK_API_KEY)$/;
374
+ function isLegacyAllowedName(name) {
375
+ return LEGACY_ALLOWED_NAME.test(name);
376
+ }
377
+ function isReadableFile(file) {
378
+ try {
379
+ return fs.statSync(file).isFile();
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
384
+ function loadFile(file) {
385
+ if (!isReadableFile(file)) return;
386
+ const result = dotenvConfig({ path: file, override: false });
387
+ if (!result.error) loadedFiles.push(file);
388
+ }
389
+ function loadLegacyFile(file) {
390
+ if (!isReadableFile(file)) return;
391
+ let parsed;
392
+ try {
393
+ parsed = dotenvParse(fs.readFileSync(file));
394
+ } catch {
395
+ return;
396
+ }
397
+ for (const [name, value] of Object.entries(parsed)) {
398
+ if (isLegacyAllowedName(name) && process.env[name] === void 0) process.env[name] = value;
399
+ }
400
+ loadedFiles.push(file);
401
+ }
402
+ function loadEnv() {
403
+ if (loaded) return;
404
+ loaded = true;
405
+ loadedFiles = [];
406
+ const cwd = process.cwd();
407
+ const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os.homedir();
408
+ loadFile(path.resolve(cwd, ".env"));
409
+ if (home) loadFile(path.join(home, ".multix", ".env"));
410
+ if (legacyClaudeEnvEnabled()) {
411
+ for (const file of legacyClaudeEnvFiles(cwd, home)) loadLegacyFile(file);
412
+ }
413
+ }
414
+ function getLoadedEnvFiles() {
415
+ return [...loadedFiles];
416
+ }
417
+ function resolveKey(name) {
418
+ return process.env[name] || void 0;
419
+ }
420
+
421
+ // src/providers/atlascloud/commands/env.ts
422
+ init_errors();
423
+
424
+ // src/core/logger.ts
425
+ var ANSI = {
426
+ reset: "\x1B[0m",
427
+ bold: "\x1B[1m",
428
+ green: "\x1B[32m",
429
+ yellow: "\x1B[33m",
430
+ red: "\x1B[31m",
431
+ blue: "\x1B[34m",
432
+ cyan: "\x1B[36m",
433
+ dim: "\x1B[2m"
434
+ };
435
+ function c(color, text) {
436
+ if (process.env.NO_COLOR || !process.stdout.isTTY) return text;
437
+ return `${color}${text}${ANSI.reset}`;
438
+ }
439
+ function createLogger(opts) {
440
+ const { verbose } = opts;
441
+ return {
442
+ info(msg) {
443
+ console.log(c(ANSI.blue, ` ${msg}`));
444
+ },
445
+ success(msg) {
446
+ console.log(c(ANSI.green, `\u2713 ${msg}`));
447
+ },
448
+ warn(msg) {
449
+ console.warn(c(ANSI.yellow, `\u26A0 ${msg}`));
450
+ },
451
+ error(msg) {
452
+ console.error(c(ANSI.red, `\u2717 ${msg}`));
453
+ },
454
+ debug(msg) {
455
+ if (verbose) console.log(c(ANSI.dim, ` [debug] ${msg}`));
456
+ },
457
+ header(msg) {
458
+ const line = "=".repeat(60);
459
+ console.log(c(ANSI.bold + ANSI.cyan, `
460
+ ${line}
461
+ ${msg}
462
+ ${line}
463
+ `));
464
+ }
465
+ };
466
+ }
467
+ var defaultLogger = createLogger({ verbose: false });
468
+
469
+ // src/providers/atlascloud/commands/env.ts
470
+ init_redact();
471
+
472
+ // src/core/async-poll.ts
473
+ init_errors();
474
+ var PollTimeoutError = class extends MultixError {
475
+ constructor(message, lastStatus) {
476
+ super(message, "POLL_TIMEOUT");
477
+ this.lastStatus = lastStatus;
478
+ this.name = "PollTimeoutError";
479
+ }
480
+ lastStatus;
481
+ };
482
+ function isTransientHttpError(error) {
483
+ if (!(error instanceof HttpError)) return false;
484
+ return error.status === 0 || error.status === 429 || error.status >= 500;
485
+ }
486
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
487
+ async function pollUntil(opts) {
488
+ const now = opts.now ?? Date.now;
489
+ const sleep5 = opts.sleep ?? defaultSleep;
490
+ const random = opts.random ?? Math.random;
491
+ const isTransient = opts.isTransientError ?? isTransientHttpError;
492
+ const maxInterval = opts.maxIntervalMs ?? 15e3;
493
+ const minInterval = opts.minIntervalMs ?? 500;
494
+ const factor = opts.backoffFactor ?? 1.5;
495
+ const jitter = opts.jitter ?? true;
496
+ const deadline = now() + opts.timeoutMs;
497
+ let interval = opts.intervalMs ?? 3e3;
498
+ let attempt = 0;
499
+ let last;
500
+ let lastError;
501
+ for (; ; ) {
502
+ attempt++;
503
+ let retryAfterMs;
504
+ try {
505
+ last = await opts.fetchStatus({ remainingMs: Math.max(1, deadline - now()), attempt });
506
+ lastError = void 0;
507
+ opts.onPoll?.(last, attempt);
508
+ if (opts.isTerminal(last)) return last;
509
+ } catch (error) {
510
+ if (!isTransient(error)) throw error;
511
+ lastError = error;
512
+ if (error instanceof HttpError) retryAfterMs = error.retryAfterMs;
513
+ }
514
+ const remaining = deadline - now();
515
+ if (remaining <= 0) {
516
+ const hint = opts.resumeHint ? ` ${opts.resumeHint}` : "";
517
+ const cause = lastError instanceof Error ? ` Last error: ${lastError.message}.` : "";
518
+ throw new PollTimeoutError(
519
+ `Timed out after ${opts.timeoutMs}ms waiting for job.${cause}${hint}`,
520
+ last
521
+ );
522
+ }
523
+ let delay = retryAfterMs ?? interval;
524
+ if (jitter && retryAfterMs === void 0) delay *= 0.8 + random() * 0.4;
525
+ await sleep5(Math.min(Math.max(delay, minInterval), remaining));
526
+ interval = Math.min(interval * factor, maxInterval);
527
+ }
528
+ }
529
+
530
+ // src/providers/atlascloud/client.ts
531
+ init_errors();
532
+
533
+ // src/core/fetch-json.ts
534
+ init_errors();
535
+ var DEFAULT_TIMEOUT_MS = 12e4;
536
+ var SNIPPET_MAX = 500;
537
+ function parseRetryAfter(value, now = Date.now()) {
538
+ if (!value) return void 0;
539
+ const trimmed = value.trim();
540
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1e3);
541
+ const date = Date.parse(trimmed);
542
+ if (Number.isNaN(date)) return void 0;
543
+ return Math.max(0, date - now);
544
+ }
545
+ async function httpErrorFromResponse(response, url) {
546
+ let snippet = "";
547
+ try {
548
+ const text = await response.text();
549
+ snippet = text.slice(0, SNIPPET_MAX);
550
+ } catch {
551
+ snippet = "(could not read body)";
552
+ }
553
+ return new HttpError(
554
+ response.status,
555
+ snippet,
556
+ url,
557
+ parseRetryAfter(response.headers.get("retry-after"))
558
+ );
559
+ }
560
+ async function fetchJson(opts) {
561
+ const { url, method = "GET", headers: headers2 = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
562
+ const doFetch = opts.fetch ?? ((input, init2) => globalThis.fetch(input, init2));
563
+ const init = {
564
+ method,
565
+ headers: {
566
+ "Content-Type": "application/json",
567
+ ...headers2
568
+ },
569
+ signal: AbortSignal.timeout(timeoutMs)
570
+ };
571
+ if (body !== void 0) {
572
+ init.body = JSON.stringify(body);
573
+ }
574
+ let response;
575
+ try {
576
+ response = await doFetch(url, init);
577
+ } catch (cause) {
578
+ const msg = cause instanceof Error ? cause.message : String(cause);
579
+ throw new HttpError(0, msg, url);
580
+ }
581
+ if (!response.ok) {
582
+ throw await httpErrorFromResponse(response, url);
583
+ }
584
+ let text;
585
+ try {
586
+ text = await response.text();
587
+ } catch (cause) {
588
+ const msg = cause instanceof Error ? cause.message : String(cause);
589
+ throw new HttpError(0, `Failed to read response body: ${msg}`, url);
590
+ }
591
+ if (text.trim() === "") return void 0;
592
+ try {
593
+ return JSON.parse(text);
594
+ } catch {
595
+ throw new HttpError(
596
+ response.status,
597
+ `Invalid JSON response: ${text.slice(0, SNIPPET_MAX)}`,
598
+ url
599
+ );
600
+ }
601
+ }
602
+
603
+ // src/providers/atlascloud/prediction-output.ts
604
+ var SUCCESS_STATUSES = /* @__PURE__ */ new Set(["completed", "succeeded", "success"]);
605
+ var FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "canceled", "cancelled", "timeout"]);
606
+ function norm(status) {
607
+ return typeof status === "string" ? status.trim().toLowerCase() : "";
608
+ }
609
+ function isSuccessStatus(status) {
610
+ return SUCCESS_STATUSES.has(norm(status));
611
+ }
612
+ function isFailureStatus(status) {
613
+ return FAILURE_STATUSES.has(norm(status));
614
+ }
615
+ function isTerminalStatus(status) {
616
+ return isSuccessStatus(status) || isFailureStatus(status);
617
+ }
618
+ function predictionErrorMessage(error) {
619
+ if (error === null || error === void 0) return "";
620
+ if (typeof error === "string") return error.trim();
621
+ if (typeof error === "number" || typeof error === "boolean") return String(error);
622
+ if (typeof error === "object") {
623
+ const obj = error;
624
+ for (const key of ["message", "msg", "error", "detail", "reason"]) {
625
+ const v = obj[key];
626
+ if (typeof v === "string" && v.trim()) return v.trim();
627
+ }
628
+ try {
629
+ return JSON.stringify(error);
630
+ } catch {
631
+ return "";
632
+ }
633
+ }
634
+ return "";
635
+ }
636
+ function collectOutputs(prediction2) {
637
+ const raw = prediction2.outputs ?? prediction2.output;
638
+ const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
639
+ const out = [];
640
+ for (const item of list) {
641
+ if (typeof item === "string") {
642
+ if (item.trim()) out.push(item);
643
+ continue;
644
+ }
645
+ if (item && typeof item === "object") {
646
+ const obj = item;
647
+ const url = ["url", "download_url", "audio_url", "video_url"].map((k) => obj[k]).find((v) => typeof v === "string" && v.length > 0);
648
+ out.push(url ?? JSON.stringify(item));
649
+ }
650
+ }
651
+ return out;
652
+ }
653
+ function isMediaOutput(value) {
654
+ const s = value.trim();
655
+ return /^https?:\/\//i.test(s) || /^data:[^,]*,/i.test(s);
656
+ }
657
+ var EXT_CATEGORY = {
658
+ png: "image",
659
+ jpg: "image",
660
+ jpeg: "image",
661
+ webp: "image",
662
+ gif: "image",
663
+ bmp: "image",
664
+ avif: "image",
665
+ mp4: "video",
666
+ webm: "video",
667
+ mov: "video",
668
+ mkv: "video",
669
+ avi: "video",
670
+ mp3: "audio",
671
+ wav: "audio",
672
+ aac: "audio",
673
+ m4a: "audio",
674
+ flac: "audio",
675
+ ogg: "audio",
676
+ opus: "audio",
677
+ pcm: "audio",
678
+ glb: "3d",
679
+ gltf: "3d",
680
+ obj: "3d",
681
+ fbx: "3d",
682
+ stl: "3d",
683
+ ply: "3d",
684
+ usdz: "3d",
685
+ usdc: "3d",
686
+ usd: "3d",
687
+ zip: "3d"
688
+ };
689
+ var MIME_EXT = {
690
+ "image/png": "png",
691
+ "image/jpeg": "jpg",
692
+ "image/webp": "webp",
693
+ "image/gif": "gif",
694
+ "video/mp4": "mp4",
695
+ "video/webm": "webm",
696
+ "audio/mpeg": "mp3",
697
+ "audio/mp3": "mp3",
698
+ "audio/wav": "wav",
699
+ "audio/x-wav": "wav",
700
+ "audio/ogg": "ogg",
701
+ "audio/flac": "flac",
702
+ "model/gltf-binary": "glb",
703
+ "video/quicktime": "mov",
704
+ "audio/aac": "aac",
705
+ "audio/mp4": "m4a",
706
+ "application/zip": "zip"
707
+ };
708
+ function extensionForContentType(contentType) {
709
+ const mime = contentType?.split(";")[0]?.trim().toLowerCase();
710
+ return mime ? MIME_EXT[mime] : void 0;
711
+ }
712
+ function outputExtension(value) {
713
+ const s = value.trim();
714
+ const dataMatch = /^data:([^;,]+)/i.exec(s);
715
+ if (dataMatch?.[1]) return MIME_EXT[dataMatch[1].toLowerCase()];
716
+ let pathname;
717
+ try {
718
+ pathname = new URL(s).pathname;
719
+ } catch {
720
+ pathname = s.split(/[?#]/)[0] ?? s;
721
+ }
722
+ const m = /\.([a-z0-9]{2,5})$/i.exec(pathname);
723
+ if (!m?.[1]) return void 0;
724
+ const ext = m[1].toLowerCase();
725
+ return ext === "jpeg" ? "jpg" : ext;
726
+ }
727
+ function outputCategory(value) {
728
+ const ext = outputExtension(value);
729
+ return ext ? EXT_CATEGORY[ext] ?? "unknown" : "unknown";
730
+ }
731
+ function defaultExtensionFor(category) {
732
+ switch (category) {
733
+ case "image":
734
+ return "png";
735
+ case "video":
736
+ return "mp4";
737
+ case "audio":
738
+ return "mp3";
739
+ case "3d":
740
+ return "glb";
741
+ case "text":
742
+ return "txt";
743
+ default:
744
+ return "bin";
745
+ }
746
+ }
747
+ function classifyOutputs(prediction2) {
748
+ const outputs = collectOutputs(prediction2);
749
+ if (outputs.length === 0) return { mode: "empty" };
750
+ const urls = outputs.filter(isMediaOutput);
751
+ const texts = outputs.filter((o) => !isMediaOutput(o));
752
+ if (urls.length === 0) return { mode: "text", texts };
753
+ return { mode: "media", urls, texts };
754
+ }
755
+ function hasNsfwFlag(prediction2) {
756
+ return Array.isArray(prediction2.has_nsfw_contents) && prediction2.has_nsfw_contents.some(Boolean);
757
+ }
758
+ function outputFileName(predictionId, index, ext) {
759
+ const prefix = predictionId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 8) || "output";
760
+ return `atlas-${prefix}-${String(index + 1).padStart(2, "0")}.${ext}`;
761
+ }
762
+
763
+ // src/providers/atlascloud/client.ts
764
+ var ATLAS_PROVIDER = "atlascloud";
765
+ var DEFAULT_UPLOAD_TIMEOUT_MS = 3e5;
766
+ var CATALOG_TIMEOUT_MS = 15e3;
767
+ var PREDICTION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
768
+ var AtlasPredictionFailedError = class extends ProviderError {
769
+ constructor(prediction2) {
770
+ const reason = predictionErrorMessage(prediction2.error) || `status "${prediction2.status}"`;
771
+ super(`Atlas Cloud prediction ${prediction2.id} failed: ${reason}`, ATLAS_PROVIDER);
772
+ this.prediction = prediction2;
773
+ this.name = "AtlasPredictionFailedError";
774
+ }
775
+ prediction;
776
+ };
777
+ function assertValidPredictionId(id) {
778
+ if (!PREDICTION_ID_RE.test(id)) {
779
+ throw new ValidationError(`Invalid Atlas Cloud prediction id: "${id}"`);
780
+ }
781
+ }
782
+ function assertPredictionSucceeded(prediction2) {
783
+ if (isFailureStatus(prediction2.status)) throw new AtlasPredictionFailedError(prediction2);
784
+ if (!isSuccessStatus(prediction2.status)) {
785
+ throw new ProviderError(
786
+ `Atlas Cloud prediction ${prediction2.id} is not finished (status "${prediction2.status}")`,
787
+ ATLAS_PROVIDER
788
+ );
789
+ }
790
+ }
791
+ function terminalPredictionFromError(error, id) {
792
+ if (!(error instanceof HttpError) || error.status < 500) return null;
793
+ const snippet = error.snippet;
794
+ try {
795
+ const parsed = JSON.parse(snippet);
796
+ const data = parsed?.data;
797
+ if (data && typeof data === "object" && isTerminalStatus(data.status)) {
798
+ return { ...data, id: data.id || id };
799
+ }
800
+ return null;
801
+ } catch {
802
+ }
803
+ const dataAt = snippet.search(/"data"\s*:\s*\{/);
804
+ if (dataAt === -1) return null;
805
+ const inData = snippet.slice(dataAt);
806
+ const status = /"status"\s*:\s*"([^"]+)"/.exec(inData)?.[1];
807
+ if (status?.toLowerCase() !== "failed") return null;
808
+ const err2 = /"error"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(inData)?.[1];
809
+ return { id, status, error: err2 ?? snippet };
810
+ }
811
+ function envelopeData(env, what) {
812
+ if (!env || typeof env !== "object") {
813
+ throw new ProviderError(`Atlas Cloud ${what}: empty response`, ATLAS_PROVIDER);
814
+ }
815
+ if (env.code !== void 0 && env.code !== 200 && env.code !== 0 && env.data === void 0) {
816
+ throw new ProviderError(
817
+ `Atlas Cloud ${what} failed (code ${env.code}): ${env.msg || env.message || "unknown error"}`,
818
+ ATLAS_PROVIDER
819
+ );
820
+ }
821
+ if (env.data === void 0 || env.data === null) {
822
+ throw new ProviderError(`Atlas Cloud ${what}: response has no data`, ATLAS_PROVIDER);
823
+ }
824
+ return env.data;
825
+ }
826
+ var AtlasCloudClient = class {
827
+ baseUrl;
828
+ apiKey;
829
+ fetchImpl;
830
+ timeoutMs;
831
+ uploadTimeoutMs;
832
+ catalog;
833
+ constructor(opts) {
834
+ if (!opts.apiKey?.trim()) {
835
+ throw new ConfigError(
836
+ "ATLASCLOUD_API_KEY is not set. Get one at https://www.atlascloud.ai/console/api-keys"
837
+ );
838
+ }
839
+ this.apiKey = opts.apiKey.trim();
840
+ this.baseUrl = (opts.baseUrl?.trim() || DEFAULT_ATLAS_BASE_URL).replace(/\/+$/, "");
841
+ this.fetchImpl = opts.fetch;
842
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
843
+ this.uploadTimeoutMs = opts.uploadTimeoutMs ?? DEFAULT_UPLOAD_TIMEOUT_MS;
844
+ }
845
+ doFetch(input, init) {
846
+ const f = this.fetchImpl;
847
+ return f ? f(input, init) : globalThis.fetch(input, init);
848
+ }
849
+ /** Catalog and schema lookups are advisory; fail fast so a slow catalog cannot stall a command. */
850
+ catalogTimeoutMs() {
851
+ return Math.min(this.timeoutMs, CATALOG_TIMEOUT_MS);
852
+ }
853
+ authHeaders() {
854
+ return { Authorization: `Bearer ${this.apiKey}`, Accept: "application/json" };
855
+ }
856
+ /** Submit a generation job. `body` holds `model` plus the model's own fields, flattened. */
857
+ async submit(kind, body) {
858
+ const path52 = SUBMIT_PATHS[kind];
859
+ if (!path52) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
860
+ if (typeof body.model !== "string" || !body.model.trim()) {
861
+ throw new ValidationError("Atlas Cloud request body needs a non-empty `model`");
862
+ }
863
+ const env = await fetchJson({
864
+ url: `${this.baseUrl}${path52}`,
865
+ method: "POST",
866
+ headers: this.authHeaders(),
867
+ body,
868
+ timeoutMs: this.timeoutMs,
869
+ fetch: this.fetchImpl
870
+ });
871
+ const data = envelopeData(env, "submit");
872
+ if (typeof data.id !== "string" || !data.id) {
873
+ throw new ProviderError("Atlas Cloud submit returned no prediction id", ATLAS_PROVIDER);
874
+ }
875
+ return data;
876
+ }
877
+ /** Fetch a prediction once. A failed task reported as HTTP 5xx is returned, not thrown. */
878
+ async getPrediction(id, timeoutMs) {
879
+ assertValidPredictionId(id);
880
+ try {
881
+ const env = await fetchJson({
882
+ url: `${this.baseUrl}/model/prediction/${id}`,
883
+ headers: this.authHeaders(),
884
+ timeoutMs: timeoutMs ?? this.timeoutMs,
885
+ fetch: this.fetchImpl
886
+ });
887
+ const data = envelopeData(env, "prediction");
888
+ return { ...data, id: data.id || id };
889
+ } catch (error) {
890
+ const terminal = terminalPredictionFromError(error, id);
891
+ if (terminal) return terminal;
892
+ throw error;
893
+ }
894
+ }
895
+ /**
896
+ * Poll until the prediction is terminal (success or failure) or the deadline
897
+ * passes. Network errors, 429 (honouring Retry-After) and 5xx are retried.
898
+ * Throws PollTimeoutError on timeout; returns the terminal prediction otherwise.
899
+ */
900
+ async waitForPrediction(id, opts) {
901
+ assertValidPredictionId(id);
902
+ return pollUntil({
903
+ // Cap each request at the time left so one hung poll cannot overrun the deadline.
904
+ fetchStatus: ({ remainingMs }) => this.getPrediction(id, Math.ceil(Math.max(1e3, Math.min(this.timeoutMs, remainingMs)))),
905
+ isTerminal: (p) => isTerminalStatus(p.status),
906
+ timeoutMs: opts.timeoutMs,
907
+ intervalMs: opts.intervalMs ?? 3e3,
908
+ maxIntervalMs: opts.maxIntervalMs ?? 15e3,
909
+ isTransientError: isTransientHttpError,
910
+ onPoll: opts.onPoll,
911
+ resumeHint: opts.resumeHint,
912
+ sleep: opts.sleep,
913
+ now: opts.now
914
+ });
915
+ }
916
+ /**
917
+ * Upload bytes and return a temporary URL usable as model input.
918
+ * The filename extension matters: Atlas and downstream models classify by it.
919
+ */
920
+ async uploadMedia(file, filename, mimeType) {
921
+ if (!filename.trim()) throw new ValidationError("uploadMedia needs a filename");
922
+ const blob = file instanceof Blob ? file : new Blob([file], { type: mimeType ?? "application/octet-stream" });
923
+ const form = new FormData();
924
+ form.append("file", blob, filename);
925
+ const url = `${this.baseUrl}/model/uploadMedia`;
926
+ let response;
927
+ try {
928
+ response = await this.doFetch(url, {
929
+ method: "POST",
930
+ headers: this.authHeaders(),
931
+ body: form,
932
+ signal: AbortSignal.timeout(this.uploadTimeoutMs)
933
+ });
934
+ } catch (cause) {
935
+ throw new HttpError(0, cause instanceof Error ? cause.message : String(cause), url);
936
+ }
937
+ if (!response.ok) throw await httpErrorFromResponse(response, url);
938
+ const json = await response.json();
939
+ const data = json.data ?? {};
940
+ const fileUrl = [data.download_url, data.url, json.url].find(
941
+ (v) => typeof v === "string" && /^https?:\/\//i.test(v)
942
+ );
943
+ if (!fileUrl) {
944
+ throw new ProviderError(
945
+ `Atlas Cloud upload returned no URL (code ${json.code ?? "?"}: ${json.msg ?? json.message ?? "no message"})`,
946
+ ATLAS_PROVIDER
947
+ );
948
+ }
949
+ return {
950
+ url: fileUrl,
951
+ filename: typeof data.filename === "string" ? data.filename : void 0,
952
+ size: typeof data.size === "number" ? data.size : void 0,
953
+ type: typeof data.type === "string" ? data.type : void 0
954
+ };
955
+ }
956
+ /** Public model catalog, fetched once per client. */
957
+ listModels() {
958
+ this.catalog ??= fetchJson({
959
+ url: `${this.baseUrl}/models`,
960
+ timeoutMs: this.catalogTimeoutMs(),
961
+ fetch: this.fetchImpl
962
+ }).then((env) => {
963
+ const data = envelopeData(env, "model catalog");
964
+ if (!Array.isArray(data)) {
965
+ throw new ProviderError("Atlas Cloud model catalog is not a list", ATLAS_PROVIDER);
966
+ }
967
+ return data.filter((m) => m && typeof m.model === "string");
968
+ }).catch((error) => {
969
+ this.catalog = void 0;
970
+ throw error;
971
+ });
972
+ return this.catalog;
973
+ }
974
+ /** Exact (case-sensitive) catalog lookup; model ids are never rewritten. */
975
+ async findModel(model) {
976
+ return (await this.listModels()).find((m) => m.model === model);
977
+ }
978
+ /**
979
+ * Load the model's published request schema and submit endpoint.
980
+ * Returns undefined when the model is not in the public catalog or publishes no schema.
981
+ */
982
+ async getModelSpec(model) {
983
+ const entry = await this.findModel(model);
984
+ if (!entry?.schema || !/^https:\/\//i.test(entry.schema)) return void 0;
985
+ const doc = await fetchJson({
986
+ url: entry.schema,
987
+ timeoutMs: this.catalogTimeoutMs(),
988
+ fetch: this.fetchImpl
989
+ });
990
+ return parseModelSpec(model, doc);
991
+ }
992
+ };
993
+
994
+ // src/providers/atlascloud/upload-mime.ts
995
+ var UPLOAD_MIME_TYPES = {
996
+ png: "image/png",
997
+ jpg: "image/jpeg",
998
+ jpeg: "image/jpeg",
999
+ webp: "image/webp",
1000
+ gif: "image/gif",
1001
+ bmp: "image/bmp",
1002
+ avif: "image/avif",
1003
+ heic: "image/heic",
1004
+ mp4: "video/mp4",
1005
+ mov: "video/quicktime",
1006
+ webm: "video/webm",
1007
+ mkv: "video/x-matroska",
1008
+ avi: "video/x-msvideo",
1009
+ mp3: "audio/mpeg",
1010
+ wav: "audio/wav",
1011
+ m4a: "audio/mp4",
1012
+ aac: "audio/aac",
1013
+ ogg: "audio/ogg",
1014
+ flac: "audio/flac",
1015
+ opus: "audio/opus"
1016
+ };
1017
+ var UPLOAD_EXTENSIONS = Object.keys(UPLOAD_MIME_TYPES);
1018
+ function uploadMimeTypeFor(filename) {
1019
+ const ext = /\.([a-z0-9]+)$/i.exec(filename)?.[1]?.toLowerCase() ?? "";
1020
+ return UPLOAD_MIME_TYPES[ext];
1021
+ }
1022
+
1023
+ // src/providers/atlascloud/commands/env.ts
1024
+ var ATLAS_KEY_URL = "https://www.atlascloud.ai/console/api-keys";
1025
+ var MODEL_ENV = {
1026
+ image: "ATLASCLOUD_IMAGE_MODEL",
1027
+ imageEdit: "ATLASCLOUD_IMAGE_EDIT_MODEL",
1028
+ video: "ATLASCLOUD_VIDEO_MODEL",
1029
+ imageToVideo: "ATLASCLOUD_IMAGE_TO_VIDEO_MODEL",
1030
+ tts: "ATLASCLOUD_TTS_MODEL",
1031
+ stt: "ATLASCLOUD_STT_MODEL",
1032
+ textTo3d: "ATLASCLOUD_TEXT_TO_3D_MODEL",
1033
+ imageTo3d: "ATLASCLOUD_IMAGE_TO_3D_MODEL"
1034
+ };
1035
+ function atlasDefaultModel(key) {
1036
+ return resolveKey(MODEL_ENV[key])?.trim() || ATLAS_DEFAULT_MODELS[key];
1037
+ }
1038
+ function createAtlasClientFromEnv() {
1039
+ const apiKey = resolveKey("ATLASCLOUD_API_KEY")?.trim();
1040
+ if (!apiKey) {
1041
+ throw new ConfigError(`ATLASCLOUD_API_KEY is not set. Get one at ${ATLAS_KEY_URL}`);
1042
+ }
1043
+ return new AtlasCloudClient({ apiKey, baseUrl: resolveKey("ATLASCLOUD_BASE_URL") });
1044
+ }
1045
+ async function resolveMediaRef(client, ref, logger) {
1046
+ const trimmed = ref.trim();
1047
+ if (/^https?:\/\//i.test(trimmed) || /^data:/i.test(trimmed)) return trimmed;
1048
+ const abs = path2.resolve(trimmed);
1049
+ const name = path2.basename(abs);
1050
+ const mime = uploadMimeTypeFor(name);
1051
+ if (!mime) {
1052
+ throw new ValidationError(
1053
+ `Refusing to upload ${trimmed}: not a supported media file. Allowed extensions: ${UPLOAD_EXTENSIONS.join(", ")}`
1054
+ );
1055
+ }
1056
+ let stat;
1057
+ try {
1058
+ stat = await fs2.stat(abs);
1059
+ } catch {
1060
+ throw new ValidationError(`Reference file not found: ${trimmed}`);
1061
+ }
1062
+ if (!stat.isFile()) throw new ValidationError(`Reference is not a file: ${trimmed}`);
1063
+ if (stat.size === 0) throw new ValidationError(`Reference file is empty: ${trimmed}`);
1064
+ const maxBytes = maxUploadBytes();
1065
+ if (stat.size > maxBytes) {
1066
+ throw new ValidationError(
1067
+ `${trimmed} is ${formatMb(stat.size)}, above the ${formatMb(maxBytes)} upload limit (set ${MAX_UPLOAD_ENV} to change it)`
1068
+ );
1069
+ }
1070
+ logger.info(`Uploading ${name} (${Math.ceil(stat.size / 1024)} KB) to Atlas Cloud...`);
1071
+ const blob = await openAsBlob(abs, { type: mime });
1072
+ const uploaded = await client.uploadMedia(blob, name, mime);
1073
+ logger.debug(`Uploaded ${name} -> ${redactUrl(uploaded.url)}`);
1074
+ return uploaded.url;
1075
+ }
1076
+ var MAX_UPLOAD_ENV = "ATLASCLOUD_MAX_UPLOAD_MB";
1077
+ var DEFAULT_MAX_UPLOAD_MB = 200;
1078
+ function maxUploadBytes() {
1079
+ const raw = resolveKey(MAX_UPLOAD_ENV)?.trim();
1080
+ if (!raw) return DEFAULT_MAX_UPLOAD_MB * 1024 * 1024;
1081
+ const mb = Number(raw);
1082
+ if (!Number.isFinite(mb) || mb <= 0) {
1083
+ throw new ConfigError(`${MAX_UPLOAD_ENV} must be a positive number of megabytes, got "${raw}"`);
1084
+ }
1085
+ return Math.floor(mb * 1024 * 1024);
1086
+ }
1087
+ function formatMb(bytes) {
1088
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1089
+ }
1090
+ function createAtlasLogger(opts) {
1091
+ const verbose = opts.verbose ?? false;
1092
+ const base = createLogger({ verbose });
1093
+ if (!opts.stdoutReserved) return base;
1094
+ return {
1095
+ ...base,
1096
+ info: (msg) => console.error(` ${msg}`),
1097
+ success: (msg) => console.error(`\u2713 ${msg}`),
1098
+ debug: (msg) => {
1099
+ if (verbose) console.error(` [debug] ${msg}`);
1100
+ },
1101
+ header: (msg) => console.error(msg)
1102
+ };
1103
+ }
1104
+ async function resolveMediaRefs(client, refs, logger) {
1105
+ const urls = [];
1106
+ for (const ref of refs) urls.push(await resolveMediaRef(client, ref, logger));
1107
+ return urls;
1108
+ }
1109
+
1110
+ // src/providers/atlascloud/commands/prediction-runner.ts
1111
+ init_errors();
1112
+ import fs8 from "fs";
1113
+ import path7 from "path";
1114
+
1115
+ // src/core/http-client.ts
1116
+ init_errors();
1117
+ import { once } from "events";
1118
+ import fs3 from "fs";
1119
+ import path3 from "path";
1120
+ import { pipeline } from "stream/promises";
1121
+ async function httpJson(opts) {
1122
+ return fetchJson(opts);
1123
+ }
1124
+ async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS, headers2) {
1125
+ fs3.mkdirSync(path3.dirname(dest), { recursive: true });
1126
+ let response;
1127
+ try {
1128
+ response = await globalThis.fetch(url, {
1129
+ signal: AbortSignal.timeout(timeoutMs),
1130
+ ...headers2 ? { headers: headers2 } : {}
1131
+ });
1132
+ } catch (cause) {
1133
+ const msg = cause instanceof Error ? cause.message : String(cause);
1134
+ throw new HttpError(0, msg, url);
1135
+ }
1136
+ if (!response.ok) {
1137
+ throw new HttpError(response.status, "(download failed)", url);
1138
+ }
1139
+ if (!response.body) {
1140
+ throw new HttpError(0, "Response body is null", url);
1141
+ }
1142
+ const fileStream = fs3.createWriteStream(dest);
1143
+ try {
1144
+ await pipeline(response.body, fileStream);
1145
+ } catch (err2) {
1146
+ if (!fileStream.closed) await once(fileStream, "close");
1147
+ throw err2;
1148
+ }
1149
+ return response.headers.get("content-type") ?? void 0;
1150
+ }
1151
+ async function fetchBytes(urlOrDataUri) {
1152
+ if (urlOrDataUri.startsWith("data:")) {
1153
+ const commaIdx = urlOrDataUri.indexOf(",");
1154
+ if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
1155
+ const encoded = urlOrDataUri.slice(commaIdx + 1);
1156
+ return Buffer.from(encoded, "base64");
1157
+ }
1158
+ let response;
1159
+ try {
1160
+ response = await globalThis.fetch(urlOrDataUri, {
1161
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
1162
+ });
1163
+ } catch (cause) {
1164
+ const msg = cause instanceof Error ? cause.message : String(cause);
1165
+ throw new HttpError(0, msg, urlOrDataUri);
1166
+ }
1167
+ if (!response.ok) {
1168
+ throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
1169
+ }
1170
+ return Buffer.from(await response.arrayBuffer());
1171
+ }
1172
+
1173
+ // src/core/image-webp-finalize.ts
1174
+ init_errors();
1175
+ import { spawnSync } from "child_process";
1176
+ import fs5 from "fs";
1177
+ import path4 from "path";
1178
+ import { Option } from "commander";
1179
+
1180
+ // src/core/image-header.ts
1181
+ import fs4 from "fs";
1182
+ function sniffImageKind(buf) {
1183
+ if (buf.length >= 8 && buf.readUInt32BE(0) === 2303741511) return "png";
1184
+ if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "jpeg";
1185
+ if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP")
1186
+ return "webp";
1187
+ return void 0;
1188
+ }
1189
+ function readPng(buf) {
1190
+ if (buf.length < 33) return void 0;
1191
+ const width = buf.readUInt32BE(16);
1192
+ const height = buf.readUInt32BE(20);
1193
+ const colorType = buf[25];
1194
+ let hasAlpha = colorType === 4 || colorType === 6;
1195
+ let offset = 8;
1196
+ while (!hasAlpha && offset + 8 <= buf.length) {
1197
+ const len = buf.readUInt32BE(offset);
1198
+ const type = buf.toString("ascii", offset + 4, offset + 8);
1199
+ if (type === "tRNS") hasAlpha = true;
1200
+ if (type === "IDAT" || type === "IEND") break;
1201
+ offset += 12 + len;
1202
+ }
1203
+ return { kind: "png", width, height, hasAlpha };
1204
+ }
1205
+ function readJpeg(buf) {
1206
+ let offset = 2;
1207
+ while (offset + 9 < buf.length) {
1208
+ if (buf[offset] !== 255) {
1209
+ offset++;
1210
+ continue;
1211
+ }
1212
+ const marker = buf[offset + 1] ?? 0;
1213
+ if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
1214
+ return {
1215
+ kind: "jpeg",
1216
+ height: buf.readUInt16BE(offset + 5),
1217
+ width: buf.readUInt16BE(offset + 7),
1218
+ hasAlpha: false
1219
+ };
1220
+ }
1221
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
1222
+ offset += 2;
1223
+ continue;
1224
+ }
1225
+ offset += 2 + buf.readUInt16BE(offset + 2);
1226
+ }
1227
+ return void 0;
1228
+ }
1229
+ function readWebp(buf) {
1230
+ if (buf.length < 30) return void 0;
1231
+ const chunk = buf.toString("ascii", 12, 16);
1232
+ if (chunk === "VP8X") {
1233
+ const flags = buf[20] ?? 0;
1234
+ return {
1235
+ kind: "webp",
1236
+ width: 1 + buf.readUIntLE(24, 3),
1237
+ height: 1 + buf.readUIntLE(27, 3),
1238
+ hasAlpha: (flags & 16) !== 0
1239
+ };
1240
+ }
1241
+ if (chunk === "VP8L") {
1242
+ const bits = buf.readUInt32LE(21);
1243
+ return {
1244
+ kind: "webp",
1245
+ width: 1 + (bits & 16383),
1246
+ height: 1 + (bits >>> 14 & 16383),
1247
+ hasAlpha: (bits >>> 28 & 1) === 1
1248
+ };
1249
+ }
1250
+ if (chunk === "VP8 ") {
1251
+ return {
1252
+ kind: "webp",
1253
+ width: buf.readUInt16LE(26) & 16383,
1254
+ height: buf.readUInt16LE(28) & 16383,
1255
+ hasAlpha: false
1256
+ };
1257
+ }
1258
+ return void 0;
1259
+ }
1260
+ function readImageHeader(buf) {
1261
+ const kind = sniffImageKind(buf);
1262
+ if (kind === "png") return readPng(buf);
1263
+ if (kind === "jpeg") return readJpeg(buf);
1264
+ if (kind === "webp") return readWebp(buf);
1265
+ return void 0;
1266
+ }
1267
+ function readImageHeaderFromFile(file) {
1268
+ return readImageHeader(fs4.readFileSync(file));
1269
+ }
1270
+
1271
+ // src/core/image-webp-finalize.ts
1272
+ var CWEBP_ARGS = ["-q", "85", "-m", "6", "-metadata", "none"];
1273
+ var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tif", ".tiff"]);
1274
+ function imageFormatOption() {
1275
+ return new Option(
1276
+ "--image-format <mode>",
1277
+ "Final image format: webp (default, cwebp -q 85) | original (keep provider format/quality)"
1278
+ );
1279
+ }
1280
+ function noWebpOption() {
1281
+ return new Option("--no-webp", "Keep the provider's original image format and quality");
1282
+ }
1283
+ function resolveImageFormatMode(opts, explicitProviderFormat = false) {
1284
+ if (opts.webp === false && opts.imageFormat === void 0) return "original";
1285
+ const raw = opts.imageFormat ?? process.env.MULTIX_IMAGE_FORMAT;
1286
+ if (raw !== void 0 && raw !== "") {
1287
+ const mode = raw.toLowerCase();
1288
+ if (mode !== "webp" && mode !== "original") {
1289
+ throw new ValidationError(`Invalid --image-format: ${raw}. Valid: webp, original`);
1290
+ }
1291
+ return mode;
1292
+ }
1293
+ if (explicitProviderFormat) return "original";
1294
+ const ext = opts.output ? path4.extname(opts.output).toLowerCase() : "";
1295
+ if (ext && ext !== ".webp" && IMAGE_EXTS.has(ext)) return "original";
1296
+ return "webp";
1297
+ }
1298
+ function hasCwebp() {
1299
+ const res = spawnSync("cwebp", ["-version"], { stdio: "ignore" });
1300
+ return !res.error && res.status === 0;
1301
+ }
1302
+ function webpTarget(file) {
1303
+ const ext = path4.extname(file);
1304
+ return ext.toLowerCase() === ".webp" ? file : `${file.slice(0, -ext.length || void 0)}.webp`;
1305
+ }
1306
+ function convertToWebp(file, logger, keepPath = false) {
1307
+ const src = readImageHeaderFromFile(file);
1308
+ if (!src) {
1309
+ logger?.warn(`Skipping WebP conversion (unrecognized image): ${file}`);
1310
+ return file;
1311
+ }
1312
+ if (src.kind === "webp") {
1313
+ const target2 = keepPath ? file : webpTarget(file);
1314
+ if (target2 !== file) fs5.renameSync(file, target2);
1315
+ return target2;
1316
+ }
1317
+ const target = keepPath ? file : webpTarget(file);
1318
+ const tmp = `${target}.tmp-${process.pid}.webp`;
1319
+ const res = spawnSync("cwebp", [...CWEBP_ARGS, file, "-o", tmp], { encoding: "utf8" });
1320
+ if (res.error || res.status !== 0) {
1321
+ fs5.rmSync(tmp, { force: true });
1322
+ logger?.warn(`cwebp failed, keeping original ${file}: ${res.stderr || res.error?.message}`);
1323
+ return file;
1324
+ }
1325
+ const out = fs5.existsSync(tmp) ? readImageHeaderFromFile(tmp) : void 0;
1326
+ const problem = !out ? "output is not a valid WebP" : out.width !== src.width || out.height !== src.height ? `size changed ${src.width}x${src.height} -> ${out.width}x${out.height}` : src.hasAlpha && !out.hasAlpha ? "transparency lost" : void 0;
1327
+ if (problem) {
1328
+ fs5.rmSync(tmp, { force: true });
1329
+ logger?.warn(`WebP verification failed (${problem}), keeping original ${file}`);
1330
+ return file;
1331
+ }
1332
+ if (target !== file) fs5.rmSync(file, { force: true });
1333
+ fs5.renameSync(tmp, target);
1334
+ logger?.debug(`WebP verified ${out?.width}x${out?.height} alpha=${out?.hasAlpha}: ${target}`);
1335
+ return target;
1336
+ }
1337
+ function finalizeGeneratedImages(files, opts) {
1338
+ const mode = resolveImageFormatMode(opts, opts.explicitProviderFormat);
1339
+ if (mode === "original" || files.length === 0) return files;
1340
+ if (!hasCwebp()) {
1341
+ opts.logger?.warn(
1342
+ "cwebp not found on PATH; keeping original image format (install libwebp, or pass --image-format original)"
1343
+ );
1344
+ return files;
1345
+ }
1346
+ const outputAbs = opts.output ? path4.resolve(opts.output) : void 0;
1347
+ const wroteOutputDirectly = outputAbs !== void 0 && files.some((f) => path4.resolve(f) === outputAbs);
1348
+ const finals = files.map(
1349
+ (f) => convertToWebp(f, opts.logger, outputAbs !== void 0 && path4.resolve(f) === outputAbs)
1350
+ );
1351
+ if (opts.output && outputAbs && !wroteOutputDirectly && finals[0] && fs5.existsSync(outputAbs)) {
1352
+ const outExt = path4.extname(outputAbs).toLowerCase();
1353
+ if ((outExt === ".webp" || outExt === "") && path4.extname(finals[0]).toLowerCase() === ".webp") {
1354
+ fs5.copyFileSync(finals[0], outputAbs);
1355
+ }
1356
+ }
1357
+ return finals;
1358
+ }
1359
+
1360
+ // src/core/output-dir.ts
1361
+ import fs6 from "fs";
1362
+ import path5 from "path";
1363
+ var cachedOutputDir;
1364
+ function getOutputDir() {
1365
+ if (cachedOutputDir) return cachedOutputDir;
1366
+ const dir = process.env.MULTIX_OUTPUT_DIR ? path5.resolve(process.env.MULTIX_OUTPUT_DIR) : path5.resolve(process.cwd(), "multix-output");
1367
+ fs6.mkdirSync(dir, { recursive: true });
1368
+ cachedOutputDir = dir;
1369
+ return dir;
1370
+ }
1371
+
1372
+ // src/providers/atlascloud/commands/prediction-runner.ts
1373
+ init_redact();
1374
+
1375
+ // src/core/video-thumb.ts
1376
+ import fs7 from "fs";
1377
+ import path6 from "path";
1378
+ var THUMB_KEYS = /* @__PURE__ */ new Set([
1379
+ "cover_image_url",
1380
+ "coverImageUrl",
1381
+ "cover_url",
1382
+ "coverUrl",
1383
+ "thumbnail_url",
1384
+ "thumbnailUrl",
1385
+ "thumb_url",
1386
+ "thumbUrl",
1387
+ "preview_url",
1388
+ "previewUrl",
1389
+ "first_frame_url",
1390
+ "firstFrameUrl",
1391
+ "poster_url",
1392
+ "posterUrl",
1393
+ "image_url",
1394
+ "imageUrl"
1395
+ ]);
1396
+ var IMAGE_EXT_RE = /\.(jpe?g|png|webp|gif|bmp)(\?|$)/i;
1397
+ function isImageUrl(value) {
1398
+ if (typeof value !== "string") return false;
1399
+ if (!/^https?:\/\//i.test(value)) return false;
1400
+ return IMAGE_EXT_RE.test(value);
1401
+ }
1402
+ function detectThumbUrl(obj) {
1403
+ if (obj === null || obj === void 0) return null;
1404
+ if (Array.isArray(obj)) {
1405
+ for (const item of obj) {
1406
+ const found = detectThumbUrl(item);
1407
+ if (found) return found;
1408
+ }
1409
+ return null;
1410
+ }
1411
+ if (typeof obj !== "object") return null;
1412
+ const record = obj;
1413
+ for (const [key, value] of Object.entries(record)) {
1414
+ if (THUMB_KEYS.has(key) && isImageUrl(value)) return value;
1415
+ }
1416
+ for (const value of Object.values(record)) {
1417
+ if (value && typeof value === "object") {
1418
+ const found = detectThumbUrl(value);
1419
+ if (found) return found;
1420
+ }
1421
+ }
1422
+ return null;
1423
+ }
1424
+ async function downloadThumbBeside(thumbUrl, videoPath, logger) {
1425
+ const match = thumbUrl.match(IMAGE_EXT_RE);
1426
+ const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
1427
+ const dir = path6.dirname(videoPath);
1428
+ const base = path6.basename(videoPath, path6.extname(videoPath));
1429
+ const dest = path6.join(dir, `${base}_thumb.${ext}`);
1430
+ try {
1431
+ await downloadFile(thumbUrl, dest);
1432
+ logger?.success(`Saved thumb ${dest}`);
1433
+ return dest;
1434
+ } catch (e) {
1435
+ logger?.warn(`Thumb download failed: ${e instanceof Error ? e.message : String(e)}`);
1436
+ return null;
1437
+ }
1438
+ }
1439
+ async function maybeDownloadThumb(source, videoPath, opts = {}) {
1440
+ if (opts.skip) return null;
1441
+ const url = detectThumbUrl(source);
1442
+ if (!url) {
1443
+ opts.logger?.debug("No thumbnail URL detected in provider response");
1444
+ return null;
1445
+ }
1446
+ const saved = await downloadThumbBeside(url, videoPath, opts.logger);
1447
+ if (saved && opts.copyTo) {
1448
+ const copyExt = path6.extname(saved);
1449
+ const copyBase = path6.basename(opts.copyTo, path6.extname(opts.copyTo));
1450
+ const copyDir = path6.dirname(path6.resolve(opts.copyTo));
1451
+ const copyDest = path6.join(copyDir, `${copyBase}_thumb${copyExt}`);
1452
+ fs7.mkdirSync(copyDir, { recursive: true });
1453
+ fs7.copyFileSync(saved, copyDest);
1454
+ opts.logger?.success(`Copied thumb to ${copyDest}`);
1455
+ }
1456
+ return saved;
1457
+ }
1458
+
1459
+ // src/providers/atlascloud/commands/prediction-runner.ts
1460
+ function addRunOptions(cmd, cfg) {
1461
+ cmd.option("--async", "Submit and exit; print the prediction id (resume with `atlas status`)").option("--wait-timeout <ms>", "Max time to wait for completion", String(cfg.waitTimeoutMs)).option("--output <path>", "Output file (single output) or directory (path without extension)").option("--no-download", "Do not download media; print output URLs instead").option("--no-validate", "Skip client-side validation against the model's published schema");
1462
+ if (cfg.images) cmd.addOption(imageFormatOption()).addOption(noWebpOption());
1463
+ if (cfg.video) cmd.option("--no-thumb", "Skip downloading the video thumbnail if present");
1464
+ return cmd.option("-v, --verbose", "Verbose logging");
1465
+ }
1466
+ function addRefOption(cmd, description) {
1467
+ return cmd.option(
1468
+ "--ref <pathOrUrl>",
1469
+ `${description} (repeat --ref for several)`,
1470
+ (value, prev) => [...prev, value],
1471
+ []
1472
+ );
1473
+ }
1474
+ function requireRefs(refs) {
1475
+ if (!refs || refs.length === 0) {
1476
+ throw new ValidationError("Missing required option --ref <pathOrUrl>");
1477
+ }
1478
+ return refs;
1479
+ }
1480
+ function parseTimeoutMs(raw, fallback) {
1481
+ if (raw === void 0) return fallback;
1482
+ const n = Number(raw);
1483
+ if (!Number.isInteger(n) || n <= 0) {
1484
+ throw new ValidationError(`--wait-timeout must be a positive integer (ms), got "${raw}"`);
1485
+ }
1486
+ return n;
1487
+ }
1488
+ var KIND_COMMAND = {
1489
+ image: "image",
1490
+ video: "video",
1491
+ audio: "audio"
1492
+ };
1493
+ async function loadModelSpec(client, model, logger, validate) {
1494
+ if (validate === false) return void 0;
1495
+ try {
1496
+ const spec = await client.getModelSpec(model);
1497
+ if (!spec) {
1498
+ logger.warn(
1499
+ `Model ${model} is not in the Atlas Cloud public catalog or has no published schema; sending without validation.`
1500
+ );
1501
+ }
1502
+ return spec;
1503
+ } catch (e) {
1504
+ logger.warn(
1505
+ `Could not load the schema for ${model} (${e instanceof Error ? e.message : String(e)}); sending without validation.`
1506
+ );
1507
+ return void 0;
1508
+ }
1509
+ }
1510
+ function assertEndpoint(spec, expected, model) {
1511
+ if (!spec?.endpoint || spec.endpoint === expected) return;
1512
+ throw new ValidationError(
1513
+ `Model ${model} is a ${spec.endpoint} model (served by generate${capitalize(spec.endpoint)}), not ${expected}. Use \`multix atlas ${KIND_COMMAND[spec.endpoint]} ...\` or \`multix atlas run ${model} --type ${spec.endpoint} --input '{...}'\`.`
1514
+ );
1515
+ }
1516
+ function capitalize(s) {
1517
+ return s.charAt(0).toUpperCase() + s.slice(1);
1518
+ }
1519
+ function resumeCommand(id) {
1520
+ return `multix atlas status ${id} --wait --download`;
1521
+ }
1522
+ async function executePrediction(o) {
1523
+ const { client, logger, opts } = o;
1524
+ const timeoutMs = parseTimeoutMs(opts.waitTimeout, o.defaultWaitTimeoutMs);
1525
+ logger.info(`Submitting to ${o.model} (generate${capitalize(o.kind)})...`);
1526
+ const submitted = await client.submit(o.kind, { ...o.body, model: o.model });
1527
+ const id = submitted.id;
1528
+ if (opts.async) {
1529
+ console.log(id);
1530
+ console.error(`Prediction submitted. Resume with: ${resumeCommand(id)}`);
1531
+ return { predictionId: id, files: [], texts: [] };
1532
+ }
1533
+ logger.info(`Prediction ${id} \u2014 waiting up to ${Math.round(timeoutMs / 1e3)}s...`);
1534
+ const final = isTerminalStatus(submitted.status) ? submitted : await client.waitForPrediction(id, {
1535
+ timeoutMs,
1536
+ intervalMs: o.pollIntervalMs,
1537
+ resumeHint: `Resume with: ${resumeCommand(id)}`,
1538
+ onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
1539
+ });
1540
+ assertPredictionSucceeded(final);
1541
+ const delivered = await deliverPrediction(final, { logger, opts, hint: o.hint });
1542
+ return { predictionId: id, prediction: final, ...delivered };
1543
+ }
1544
+ function isDirectoryOutput(output) {
1545
+ return /[\\/]$/.test(output) || path7.extname(output) === "" || fs8.existsSync(output) && fs8.statSync(output).isDirectory();
1546
+ }
1547
+ function planOutputPaths(urls, cfg) {
1548
+ const exts = urls.map((u) => {
1549
+ const cat = outputCategory(u);
1550
+ return outputExtension(u) ?? defaultExtensionFor(cat !== "unknown" ? cat : cfg.hint ?? "unknown");
1551
+ });
1552
+ const output = cfg.output;
1553
+ if (!output || isDirectoryOutput(output)) {
1554
+ const dir = output ? path7.resolve(output) : cfg.outDir();
1555
+ return exts.map((ext, i) => path7.join(dir, outputFileName(cfg.predictionId, i, ext)));
1556
+ }
1557
+ const abs = path7.resolve(output);
1558
+ if (urls.length === 1) return [abs];
1559
+ const base = abs.slice(0, -path7.extname(abs).length);
1560
+ return exts.map((ext, i) => `${base}-${String(i + 1).padStart(2, "0")}.${ext}`);
1561
+ }
1562
+ async function deliverPrediction(prediction2, cfg) {
1563
+ const { logger, opts } = cfg;
1564
+ if (prediction2.price !== void 0 && prediction2.price !== "") {
1565
+ logger.info(`Cost: $${prediction2.price}`);
1566
+ }
1567
+ const classified = classifyOutputs(prediction2);
1568
+ if (classified.mode === "empty") {
1569
+ throw new ProviderError(
1570
+ hasNsfwFlag(prediction2) ? `Prediction ${prediction2.id} returned no output: blocked by content safety (has_nsfw_contents). Try a different prompt or input.` : `Prediction ${prediction2.id} completed but returned no outputs.`,
1571
+ ATLAS_PROVIDER
1572
+ );
1573
+ }
1574
+ for (const text of classified.texts) console.log(text);
1575
+ if (classified.mode === "text") {
1576
+ if (opts.output) {
1577
+ const dest = isDirectoryOutput(opts.output) ? path7.resolve(opts.output, outputFileName(prediction2.id, 0, "txt")) : path7.resolve(opts.output);
1578
+ fs8.mkdirSync(path7.dirname(dest), { recursive: true });
1579
+ fs8.writeFileSync(dest, `${classified.texts.join("\n\n")}
1580
+ `, "utf8");
1581
+ logger.success(`Saved text output to ${dest}`);
1582
+ return { files: [dest], texts: classified.texts };
1583
+ }
1584
+ return { files: [], texts: classified.texts };
1585
+ }
1586
+ if (opts.download === false) {
1587
+ for (const u of classified.urls) console.log(u);
1588
+ return { files: [], texts: classified.texts };
1589
+ }
1590
+ const dests = planOutputPaths(classified.urls, {
1591
+ predictionId: prediction2.id,
1592
+ outDir: getOutputDir,
1593
+ output: opts.output,
1594
+ hint: cfg.hint
1595
+ });
1596
+ const explicitFile = classified.urls.length === 1 && opts.output !== void 0 && !isDirectoryOutput(opts.output);
1597
+ const saved = [];
1598
+ let failed = 0;
1599
+ for (let i = 0; i < classified.urls.length; i++) {
1600
+ const url = classified.urls[i];
1601
+ const planned = dests[i];
1602
+ const part = `${planned}.part`;
1603
+ try {
1604
+ const contentType = await downloadFile(url, part);
1605
+ const dest = explicitFile || outputExtension(url) ? planned : withContentTypeExtension(planned, contentType);
1606
+ fs8.renameSync(part, dest);
1607
+ saved.push(dest);
1608
+ logger.debug(`Downloaded ${redactUrl(url)} -> ${dest}`);
1609
+ } catch (e) {
1610
+ fs8.rmSync(part, { force: true });
1611
+ failed++;
1612
+ logger.warn(
1613
+ `Download failed (${redactUrl(url)}): ${e instanceof Error ? e.message : String(e)}`
1614
+ );
1615
+ }
1616
+ }
1617
+ if (saved.length === 0) {
1618
+ throw new ProviderError(
1619
+ `Prediction ${prediction2.id} produced ${classified.urls.length} output(s) but none downloaded. Retry with: ${resumeCommand(prediction2.id)}`,
1620
+ ATLAS_PROVIDER
1621
+ );
1622
+ }
1623
+ const images = saved.filter((f) => outputCategory(f) === "image");
1624
+ const fileOutput = opts.output && !isDirectoryOutput(opts.output) ? opts.output : void 0;
1625
+ const finalImages = finalizeGeneratedImages(images, {
1626
+ imageFormat: opts.imageFormat,
1627
+ webp: opts.webp,
1628
+ output: fileOutput,
1629
+ logger
1630
+ });
1631
+ const files = saved.map((f) => finalImages[images.indexOf(f)] ?? f);
1632
+ const firstVideo = files.find((f) => outputCategory(f) === "video");
1633
+ if (firstVideo) {
1634
+ await maybeDownloadThumb(prediction2, firstVideo, { skip: opts.thumb === false, logger });
1635
+ }
1636
+ console.log(`
1637
+ Prediction ${prediction2.id}: saved ${files.length} file(s)`);
1638
+ for (const f of files) console.log(` ${f}`);
1639
+ if (failed > 0) {
1640
+ throw new ProviderError(
1641
+ `Prediction ${prediction2.id}: downloaded ${saved.length} of ${classified.urls.length} output(s); ${failed} failed. Saved: ${files.join(", ")}. Retry with: ${resumeCommand(prediction2.id)}`,
1642
+ ATLAS_PROVIDER
1643
+ );
1644
+ }
1645
+ return { files, texts: classified.texts };
1646
+ }
1647
+ function withContentTypeExtension(planned, contentType) {
1648
+ const ext = extensionForContentType(contentType);
1649
+ if (!ext) return planned;
1650
+ const current = path7.extname(planned);
1651
+ return `${current ? planned.slice(0, -current.length) : planned}.${ext}`;
1652
+ }
1653
+
1654
+ // src/providers/atlascloud/commands/generation-command.ts
1655
+ async function runGenerationCommand(cfg) {
1656
+ const logger = createAtlasLogger({
1657
+ verbose: cfg.opts.verbose,
1658
+ stdoutReserved: cfg.opts.async
1659
+ });
1660
+ const client = createAtlasClientFromEnv();
1661
+ const spec = await loadModelSpec(client, cfg.model, logger, cfg.opts.validate);
1662
+ const kind = cfg.endpointFromSchema ? spec?.endpoint ?? cfg.kind : cfg.kind;
1663
+ assertEndpoint(spec, kind, cfg.model);
1664
+ const refs = cfg.refs ?? [];
1665
+ buildModelInput(spec, cfg.requests(refs));
1666
+ const refUrls = refs.length > 0 ? await resolveMediaRefs(client, refs, logger) : [];
1667
+ const body = buildModelInput(spec, cfg.requests(refUrls));
1668
+ logger.debug(`Request fields: ${Object.keys(body).join(", ")}`);
1669
+ await executePrediction({
1670
+ client,
1671
+ logger,
1672
+ kind,
1673
+ model: cfg.model,
1674
+ body,
1675
+ opts: cfg.opts,
1676
+ hint: cfg.hint,
1677
+ defaultWaitTimeoutMs: cfg.defaultWaitTimeoutMs,
1678
+ pollIntervalMs: cfg.pollIntervalMs
1679
+ });
1680
+ }
1681
+
1682
+ // src/providers/atlascloud/commands/audio.ts
1683
+ var AUDIO_WAIT_MS = 6e5;
1684
+ var AUDIO_POLL_MS = 3e3;
1685
+ function registerAtlasAudioCommand(parent) {
1686
+ const audio = parent.command("audio [text]").description(
1687
+ `Speech, music, lyrics or transcription via Atlas Cloud (default TTS: ${ATLAS_DEFAULT_MODELS.tts}; with --audio, STT: ${ATLAS_DEFAULT_MODELS.stt})`
1688
+ ).option(
1689
+ "-m, --model <id>",
1690
+ "Atlas Cloud audio model id (env: ATLASCLOUD_TTS_MODEL / ATLASCLOUD_STT_MODEL)"
1691
+ ).option("--audio <pathOrUrl>", "Input audio for speech-to-text (local files are uploaded)").option("--voice <id>", "Voice id (model-dependent)").option("--format <fmt>", "Output audio format, e.g. mp3, wav (model-dependent)").option("--language <code>", "Language (model-dependent)");
1692
+ addRunOptions(audio, { waitTimeoutMs: AUDIO_WAIT_MS }).action(
1693
+ async (text, opts) => {
1694
+ if (!text && !opts.audio) {
1695
+ throw new ValidationError(
1696
+ "Provide text to synthesize, or --audio <pathOrUrl> to transcribe"
1697
+ );
1698
+ }
1699
+ const model = opts.model ?? atlasDefaultModel(opts.audio ? "stt" : "tts");
1700
+ await runGenerationCommand({
1701
+ model,
1702
+ kind: "audio",
1703
+ refs: opts.audio ? [opts.audio] : [],
1704
+ requests: (refs) => [
1705
+ { flag: "[text]", value: text, candidates: FIELD_CANDIDATES.speechText },
1706
+ { flag: "--audio", value: refs[0], candidates: FIELD_CANDIDATES.audioInput },
1707
+ { flag: "--voice", value: opts.voice, candidates: FIELD_CANDIDATES.voice },
1708
+ { flag: "--format", value: opts.format, candidates: FIELD_CANDIDATES.audioFormat },
1709
+ { flag: "--language", value: opts.language, candidates: FIELD_CANDIDATES.language }
1710
+ ],
1711
+ opts,
1712
+ hint: "audio",
1713
+ defaultWaitTimeoutMs: AUDIO_WAIT_MS,
1714
+ pollIntervalMs: AUDIO_POLL_MS
1715
+ });
1716
+ }
1717
+ );
1718
+ }
1719
+
1720
+ // src/providers/atlascloud/commands/image.ts
1721
+ var IMAGE_WAIT_MS = 3e5;
1722
+ var IMAGE_POLL_MS = 2e3;
1723
+ function imageRequests(prompt, o) {
1724
+ return [
1725
+ { flag: "--prompt", value: prompt, candidates: FIELD_CANDIDATES.prompt },
1726
+ { flag: "--size", value: o.size, candidates: FIELD_CANDIDATES.size },
1727
+ { flag: "--aspect-ratio", value: o.aspectRatio, candidates: FIELD_CANDIDATES.aspectRatio },
1728
+ { flag: "--resolution", value: o.resolution, candidates: FIELD_CANDIDATES.resolution },
1729
+ { flag: "--num-images", value: o.numImages, candidates: FIELD_CANDIDATES.numImages },
1730
+ { flag: "--seed", value: o.seed, candidates: FIELD_CANDIDATES.seed },
1731
+ { flag: "--negative-prompt", value: o.negativePrompt, candidates: FIELD_CANDIDATES.negativePrompt }
1732
+ ];
1733
+ }
1734
+ function addImageFlags(cmd) {
1735
+ return cmd.option("--size <size>", "Output size in the model's format (e.g. 1024*1024 or 1024x1024)").option("--aspect-ratio <ratio>", "Aspect ratio, e.g. 1:1, 16:9 (model-dependent)").option("--resolution <res>", "Resolution tier, e.g. 1k, 2k (model-dependent)").option("-n, --num-images <n>", "Number of images (model-dependent)").option("--seed <n>", "Seed").option("--negative-prompt <text>", "Negative prompt (model-dependent)");
1736
+ }
1737
+ function registerAtlasImageCommands(parent) {
1738
+ const image = parent.command("image <prompt>").description(`Text-to-image via Atlas Cloud (default: ${ATLAS_DEFAULT_MODELS.image})`).option("-m, --model <id>", "Atlas Cloud image model id (env: ATLASCLOUD_IMAGE_MODEL)");
1739
+ addImageFlags(image);
1740
+ addRunOptions(image, { waitTimeoutMs: IMAGE_WAIT_MS, images: true }).action(
1741
+ async (prompt, opts) => {
1742
+ await runGenerationCommand({
1743
+ model: opts.model ?? atlasDefaultModel("image"),
1744
+ kind: "image",
1745
+ requests: () => imageRequests(prompt, opts),
1746
+ opts,
1747
+ hint: "image",
1748
+ defaultWaitTimeoutMs: IMAGE_WAIT_MS,
1749
+ pollIntervalMs: IMAGE_POLL_MS
1750
+ });
1751
+ }
1752
+ );
1753
+ const edit = parent.command("image-to-image").description(
1754
+ `Edit or restyle reference images via Atlas Cloud (default: ${ATLAS_DEFAULT_MODELS.imageEdit})`
1755
+ ).requiredOption("--prompt <text>", "Edit instruction");
1756
+ addRefOption(edit, "Reference image; local files are uploaded first; required").option(
1757
+ "-m, --model <id>",
1758
+ "Atlas Cloud image-edit model id (env: ATLASCLOUD_IMAGE_EDIT_MODEL)"
1759
+ );
1760
+ addImageFlags(edit);
1761
+ addRunOptions(edit, { waitTimeoutMs: IMAGE_WAIT_MS, images: true }).action(
1762
+ async (opts) => {
1763
+ await runGenerationCommand({
1764
+ model: opts.model ?? atlasDefaultModel("imageEdit"),
1765
+ kind: "image",
1766
+ refs: requireRefs(opts.ref),
1767
+ requests: (refs) => [
1768
+ ...imageRequests(opts.prompt, opts),
1769
+ { flag: "--ref", value: refs, candidates: FIELD_CANDIDATES.editImages }
1770
+ ],
1771
+ opts,
1772
+ hint: "image",
1773
+ defaultWaitTimeoutMs: IMAGE_WAIT_MS,
1774
+ pollIntervalMs: IMAGE_POLL_MS
1775
+ });
1776
+ }
1777
+ );
1778
+ }
1779
+
1780
+ // src/providers/atlascloud/commands/run.ts
1781
+ init_errors();
1782
+
1783
+ // src/core/json-input.ts
1784
+ init_errors();
1785
+ import fs9 from "fs";
1786
+ function parseJsonInput(raw, flag = "--input") {
1787
+ let text;
1788
+ if (raw.startsWith("@")) {
1789
+ try {
1790
+ text = fs9.readFileSync(raw.slice(1), "utf8");
1791
+ } catch (e) {
1792
+ const msg = e instanceof Error ? e.message : String(e);
1793
+ throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
1794
+ }
1795
+ } else {
1796
+ text = raw;
1797
+ }
1798
+ try {
1799
+ const parsed = JSON.parse(text);
1800
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1801
+ throw new Error("input must be a JSON object");
1802
+ }
1803
+ return parsed;
1804
+ } catch (e) {
1805
+ const msg = e instanceof Error ? e.message : String(e);
1806
+ throw new ValidationError(`Invalid ${flag} JSON: ${msg}`);
1807
+ }
1808
+ }
1809
+
1810
+ // src/providers/atlascloud/commands/run.ts
1811
+ var RUN_WAIT_MS = 6e5;
1812
+ function parseMediaKind(raw) {
1813
+ if (raw === void 0) return void 0;
1814
+ const kind = raw.toLowerCase();
1815
+ if (!ATLAS_MEDIA_KINDS.includes(kind)) {
1816
+ throw new ValidationError(`Invalid --type "${raw}". Valid: ${ATLAS_MEDIA_KINDS.join(", ")}`);
1817
+ }
1818
+ return kind;
1819
+ }
1820
+ function registerAtlasRunCommand(parent) {
1821
+ const run = parent.command("run <modelId>").description("Run any Atlas Cloud model with a raw JSON payload (fields per the model's docs)").requiredOption("--input <json>", "Model input as JSON, or @path/to/input.json").option(
1822
+ "--type <type>",
1823
+ "Endpoint: image | video | audio (default: from the model's published schema; 3D uses image). Required with --no-validate or for models without a schema"
1824
+ );
1825
+ addRunOptions(run, { waitTimeoutMs: RUN_WAIT_MS, images: true, video: true }).action(
1826
+ async (modelId, opts) => {
1827
+ const logger = createAtlasLogger({ verbose: opts.verbose, stdoutReserved: opts.async });
1828
+ const input = parseJsonInput(opts.input);
1829
+ if (input.model !== void 0 && input.model !== modelId) {
1830
+ throw new ValidationError(
1831
+ `--input.model "${String(input.model)}" does not match <modelId> "${modelId}"`
1832
+ );
1833
+ }
1834
+ const explicit = parseMediaKind(opts.type);
1835
+ const client = createAtlasClientFromEnv();
1836
+ const spec = await loadModelSpec(client, modelId, logger, opts.validate);
1837
+ const kind = explicit ?? spec?.endpoint;
1838
+ if (!kind) {
1839
+ throw new ValidationError(
1840
+ `Cannot determine the endpoint for ${modelId}; pass --type ${ATLAS_MEDIA_KINDS.join("|")}`
1841
+ );
1842
+ }
1843
+ assertEndpoint(spec, kind, modelId);
1844
+ if (spec) {
1845
+ const unknown = validateRawInput(spec, input);
1846
+ if (unknown.length > 0) {
1847
+ logger.warn(
1848
+ `Fields not in ${modelId}'s published schema (sent anyway): ${unknown.join(", ")}`
1849
+ );
1850
+ }
1851
+ }
1852
+ await executePrediction({
1853
+ client,
1854
+ logger,
1855
+ kind,
1856
+ model: modelId,
1857
+ body: input,
1858
+ opts,
1859
+ defaultWaitTimeoutMs: RUN_WAIT_MS,
1860
+ pollIntervalMs: kind === "video" ? 5e3 : 3e3
1861
+ });
1862
+ }
1863
+ );
1864
+ }
1865
+
1866
+ // src/providers/atlascloud/commands/status.ts
1867
+ var STATUS_WAIT_MS = 6e5;
1868
+ function registerAtlasStatusCommand(parent) {
1869
+ parent.command("status <predictionId>").description("Get an Atlas Cloud prediction; --wait to poll, --download to save outputs").option("--wait", "Poll until the prediction finishes").option("--wait-timeout <ms>", "Max time to wait (with --wait)", String(STATUS_WAIT_MS)).option("--download", "Save outputs when finished (implies --wait)").option("--output <path>", "Output file (single output) or directory (path without extension)").addOption(imageFormatOption()).addOption(noWebpOption()).option("--no-thumb", "Skip downloading the video thumbnail if present").option("-v, --verbose", "Verbose logging").action(async (predictionId, opts) => {
1870
+ const logger = createLogger({ verbose: opts.verbose ?? false });
1871
+ const client = createAtlasClientFromEnv();
1872
+ const wait = Boolean(opts.wait || opts.download);
1873
+ const prediction2 = wait ? await client.waitForPrediction(predictionId, {
1874
+ timeoutMs: parseTimeoutMs(opts.waitTimeout, STATUS_WAIT_MS),
1875
+ resumeHint: `Resume with: ${resumeCommand(predictionId)}`,
1876
+ onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
1877
+ }) : await client.getPrediction(predictionId);
1878
+ if (!opts.download) {
1879
+ console.log(JSON.stringify(prediction2, null, 2));
1880
+ if (isFailureStatus(prediction2.status)) throw new AtlasPredictionFailedError(prediction2);
1881
+ return;
1882
+ }
1883
+ assertPredictionSucceeded(prediction2);
1884
+ await deliverPrediction(prediction2, { logger, opts: { ...opts, download: true } });
1885
+ });
1886
+ }
1887
+
1888
+ // src/providers/atlascloud/commands/three-d.ts
1889
+ init_errors();
1890
+ var THREE_D_WAIT_MS = 9e5;
1891
+ var THREE_D_POLL_MS = 5e3;
1892
+ function registerAtlas3dCommand(parent) {
1893
+ const cmd = parent.command("3d [prompt]").alias("generate-3d").description(
1894
+ `Text- or image-to-3D via Atlas Cloud (defaults: ${ATLAS_DEFAULT_MODELS.textTo3d}; with --ref ${ATLAS_DEFAULT_MODELS.imageTo3d})`
1895
+ );
1896
+ addRefOption(cmd, "Reference image for image-to-3D (local files are uploaded)").option(
1897
+ "-m, --model <id>",
1898
+ "Atlas Cloud 3D model id (env: ATLASCLOUD_TEXT_TO_3D_MODEL / ATLASCLOUD_IMAGE_TO_3D_MODEL)"
1899
+ ).option("--format <fmt>", "Mesh format, e.g. GLB, OBJ, FBX (model-dependent)").option("--seed <n>", "Seed (model-dependent)");
1900
+ addRunOptions(cmd, { waitTimeoutMs: THREE_D_WAIT_MS }).action(
1901
+ async (prompt, opts) => {
1902
+ const refs = opts.ref ?? [];
1903
+ if (!prompt && refs.length === 0) {
1904
+ throw new ValidationError("Provide a prompt (text-to-3D) or --ref <image> (image-to-3D)");
1905
+ }
1906
+ await runGenerationCommand({
1907
+ model: opts.model ?? atlasDefaultModel(refs.length > 0 ? "imageTo3d" : "textTo3d"),
1908
+ kind: "image",
1909
+ endpointFromSchema: true,
1910
+ refs,
1911
+ requests: (urls) => [
1912
+ { flag: "[prompt]", value: prompt, candidates: FIELD_CANDIDATES.prompt },
1913
+ { flag: "--ref", value: urls, candidates: FIELD_CANDIDATES.primaryImage },
1914
+ { flag: "--format", value: opts.format, candidates: FIELD_CANDIDATES.modelFormat },
1915
+ { flag: "--seed", value: opts.seed, candidates: FIELD_CANDIDATES.seed }
1916
+ ],
1917
+ opts,
1918
+ hint: "3d",
1919
+ defaultWaitTimeoutMs: THREE_D_WAIT_MS,
1920
+ pollIntervalMs: THREE_D_POLL_MS
1921
+ });
1922
+ }
1923
+ );
1924
+ }
1925
+
1926
+ // src/providers/atlascloud/commands/upload.ts
1927
+ function registerAtlasUploadCommand(parent) {
1928
+ parent.command("upload <file>").description("Upload a local file to Atlas Cloud and print its temporary URL").option("-v, --verbose", "Verbose logging").action(async (file, opts) => {
1929
+ const logger = createAtlasLogger({ verbose: opts.verbose, stdoutReserved: true });
1930
+ if (/^(https?|data):/i.test(file.trim())) {
1931
+ logger.warn("Input is already a URL; nothing to upload.");
1932
+ console.log(file.trim());
1933
+ return;
1934
+ }
1935
+ const client = createAtlasClientFromEnv();
1936
+ console.log(await resolveMediaRef(client, file, logger));
1937
+ });
1938
+ }
1939
+
1940
+ // src/providers/atlascloud/commands/video.ts
1941
+ var VIDEO_WAIT_MS = 9e5;
1942
+ var VIDEO_POLL_MS = 5e3;
1943
+ function videoRequests(prompt, o) {
1944
+ return [
1945
+ { flag: "--prompt", value: prompt, candidates: FIELD_CANDIDATES.prompt },
1946
+ { flag: "--duration", value: o.duration, candidates: FIELD_CANDIDATES.duration },
1947
+ { flag: "--resolution", value: o.resolution, candidates: FIELD_CANDIDATES.resolution },
1948
+ { flag: "--aspect-ratio", value: o.aspectRatio, candidates: FIELD_CANDIDATES.aspectRatio },
1949
+ { flag: "--negative-prompt", value: o.negativePrompt, candidates: FIELD_CANDIDATES.negativePrompt },
1950
+ { flag: "--seed", value: o.seed, candidates: FIELD_CANDIDATES.seed }
1951
+ ];
1952
+ }
1953
+ function addVideoFlags(cmd) {
1954
+ return cmd.option("--duration <s>", "Duration in seconds (model-dependent values)").option("--resolution <res>", "Resolution, e.g. 480p, 720p, 1080p (model-dependent)").option("--aspect-ratio <ratio>", "Aspect ratio, e.g. 16:9, 9:16 (model-dependent)").option("--negative-prompt <text>", "Negative prompt (model-dependent)").option("--seed <n>", "Seed");
1955
+ }
1956
+ function registerAtlasVideoCommands(parent) {
1957
+ const video = parent.command("video <prompt>").description(`Text-to-video via Atlas Cloud (default: ${ATLAS_DEFAULT_MODELS.video})`).option("-m, --model <id>", "Atlas Cloud video model id (env: ATLASCLOUD_VIDEO_MODEL)");
1958
+ addVideoFlags(video);
1959
+ addRunOptions(video, { waitTimeoutMs: VIDEO_WAIT_MS, video: true }).action(
1960
+ async (prompt, opts) => {
1961
+ await runGenerationCommand({
1962
+ model: opts.model ?? atlasDefaultModel("video"),
1963
+ kind: "video",
1964
+ requests: () => videoRequests(prompt, opts),
1965
+ opts,
1966
+ hint: "video",
1967
+ defaultWaitTimeoutMs: VIDEO_WAIT_MS,
1968
+ pollIntervalMs: VIDEO_POLL_MS
1969
+ });
1970
+ }
1971
+ );
1972
+ const i2v = parent.command("image-to-video").description(
1973
+ `Animate reference image(s) via Atlas Cloud (default: ${ATLAS_DEFAULT_MODELS.imageToVideo})`
1974
+ ).option("--prompt <text>", "Motion / scene prompt");
1975
+ addRefOption(i2v, "Reference image; local files are uploaded first; required").option(
1976
+ "-m, --model <id>",
1977
+ "Atlas Cloud image-to-video model id (env: ATLASCLOUD_IMAGE_TO_VIDEO_MODEL)"
1978
+ );
1979
+ addVideoFlags(i2v);
1980
+ addRunOptions(i2v, { waitTimeoutMs: VIDEO_WAIT_MS, video: true }).action(
1981
+ async (opts) => {
1982
+ await runGenerationCommand({
1983
+ model: opts.model ?? atlasDefaultModel("imageToVideo"),
1984
+ kind: "video",
1985
+ refs: requireRefs(opts.ref),
1986
+ requests: (refs) => [
1987
+ ...videoRequests(opts.prompt, opts),
1988
+ { flag: "--ref", value: refs, candidates: FIELD_CANDIDATES.primaryImage }
1989
+ ],
1990
+ opts,
1991
+ hint: "video",
1992
+ defaultWaitTimeoutMs: VIDEO_WAIT_MS,
1993
+ pollIntervalMs: VIDEO_POLL_MS
1994
+ });
1995
+ }
1996
+ );
1997
+ }
1998
+
1999
+ // src/providers/atlascloud/commands/index.ts
2000
+ function registerAtlasCloudCommands(program2) {
2001
+ const atlas = program2.command("atlas").alias("atlascloud").description(
2002
+ "Atlas Cloud: image, image-to-image, video, image-to-video, audio (TTS/STT/music), 3D, generic model runner, async status"
2003
+ );
2004
+ registerAtlasImageCommands(atlas);
2005
+ registerAtlasVideoCommands(atlas);
2006
+ registerAtlasAudioCommand(atlas);
2007
+ registerAtlas3dCommand(atlas);
2008
+ registerAtlasRunCommand(atlas);
2009
+ registerAtlasStatusCommand(atlas);
2010
+ registerAtlasUploadCommand(atlas);
2011
+ }
2012
+
102
2013
  // src/providers/byteplus/commands/generate-3d.ts
103
- import path5 from "path";
2014
+ import path9 from "path";
104
2015
 
105
2016
  // src/core/image-input.ts
106
- import fs from "fs/promises";
107
- import path from "path";
2017
+ import fs10 from "fs/promises";
2018
+ import path8 from "path";
108
2019
  var IMAGE_MIME_BY_EXT = {
109
2020
  ".jpg": "image/jpeg",
110
2021
  ".jpeg": "image/jpeg",
@@ -131,7 +2042,7 @@ function isHttpUrl(input) {
131
2042
  return /^https?:\/\//i.test(input);
132
2043
  }
133
2044
  async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
134
- const ext = path.extname(filePath).toLowerCase();
2045
+ const ext = path8.extname(filePath).toLowerCase();
135
2046
  const mime = mimeTable[ext];
136
2047
  if (!mime) {
137
2048
  throw new Error(
@@ -140,7 +2051,7 @@ async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
140
2051
  }
141
2052
  const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
142
2053
  const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
143
- const buf = await fs.readFile(filePath);
2054
+ const buf = await fs10.readFile(filePath);
144
2055
  if (buf.byteLength > hard) {
145
2056
  throw new Error(
146
2057
  `${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
@@ -183,65 +2094,8 @@ function normalizeOpts(opts) {
183
2094
  return opts;
184
2095
  }
185
2096
 
186
- // src/core/logger.ts
187
- var ANSI = {
188
- reset: "\x1B[0m",
189
- bold: "\x1B[1m",
190
- green: "\x1B[32m",
191
- yellow: "\x1B[33m",
192
- red: "\x1B[31m",
193
- blue: "\x1B[34m",
194
- cyan: "\x1B[36m",
195
- dim: "\x1B[2m"
196
- };
197
- function c(color, text) {
198
- if (process.env.NO_COLOR || !process.stdout.isTTY) return text;
199
- return `${color}${text}${ANSI.reset}`;
200
- }
201
- function createLogger(opts) {
202
- const { verbose } = opts;
203
- return {
204
- info(msg) {
205
- console.log(c(ANSI.blue, ` ${msg}`));
206
- },
207
- success(msg) {
208
- console.log(c(ANSI.green, `\u2713 ${msg}`));
209
- },
210
- warn(msg) {
211
- console.warn(c(ANSI.yellow, `\u26A0 ${msg}`));
212
- },
213
- error(msg) {
214
- console.error(c(ANSI.red, `\u2717 ${msg}`));
215
- },
216
- debug(msg) {
217
- if (verbose) console.log(c(ANSI.dim, ` [debug] ${msg}`));
218
- },
219
- header(msg) {
220
- const line = "=".repeat(60);
221
- console.log(c(ANSI.bold + ANSI.cyan, `
222
- ${line}
223
- ${msg}
224
- ${line}
225
- `));
226
- }
227
- };
228
- }
229
- var defaultLogger = createLogger({ verbose: false });
230
-
231
- // src/core/output-dir.ts
232
- import fs2 from "fs";
233
- import path2 from "path";
234
- var cachedOutputDir;
235
- function getOutputDir() {
236
- if (cachedOutputDir) return cachedOutputDir;
237
- const dir = process.env.MULTIX_OUTPUT_DIR ? path2.resolve(process.env.MULTIX_OUTPUT_DIR) : path2.resolve(process.cwd(), "multix-output");
238
- fs2.mkdirSync(dir, { recursive: true });
239
- cachedOutputDir = dir;
240
- return dir;
241
- }
242
-
243
2097
  // src/providers/leonardo/poll.ts
244
- var PollTimeoutError = class extends Error {
2098
+ var PollTimeoutError2 = class extends Error {
245
2099
  constructor() {
246
2100
  super("Polling timed out before the operation completed.");
247
2101
  this.name = "PollTimeoutError";
@@ -272,221 +2126,11 @@ async function poll(opts) {
272
2126
  });
273
2127
  });
274
2128
  }
275
- throw new PollTimeoutError();
276
- }
277
-
278
- // src/core/env-loader.ts
279
- init_redact();
280
- import fs3 from "fs";
281
- import os from "os";
282
- import path3 from "path";
283
- import { config as dotenvConfig, parse as dotenvParse } from "dotenv";
284
- var loaded = false;
285
- var loadedFiles = [];
286
- var LEGACY_SKILL_DIRS = ["ai-multimodal", "ak-ai-multimodal"];
287
- function legacyFilesUnder(root) {
288
- const claude = path3.join(root, ".claude");
289
- return [
290
- ...LEGACY_SKILL_DIRS.map((dir) => path3.join(claude, "skills", dir, ".env")),
291
- path3.join(claude, "skills", ".env"),
292
- path3.join(claude, ".env")
293
- ];
294
- }
295
- function samePath(a, b) {
296
- const norm = (p) => {
297
- const r = path3.resolve(p);
298
- return process.platform === "win32" ? r.toLowerCase() : r;
299
- };
300
- return norm(a) === norm(b);
301
- }
302
- function legacyClaudeEnvFiles(cwd, home) {
303
- const files = legacyFilesUnder(cwd);
304
- if (home && !samePath(home, cwd)) files.push(...legacyFilesUnder(home));
305
- return files;
306
- }
307
- function legacyClaudeEnvEnabled(env = process.env) {
308
- const value = env.MULTIX_LEGACY_CLAUDE_ENV;
309
- return value !== void 0 && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
310
- }
311
- var LEGACY_ALLOWED_NAME = /^[A-Z][A-Z0-9_]*_(API_KEY(_\d+)?|API_TOKEN|ACCOUNT_ID|GATEWAY_ID)$|^(FAL_KEY|ARK_API_KEY)$/;
312
- function isLegacyAllowedName(name) {
313
- return LEGACY_ALLOWED_NAME.test(name);
314
- }
315
- function isReadableFile(file) {
316
- try {
317
- return fs3.statSync(file).isFile();
318
- } catch {
319
- return false;
320
- }
321
- }
322
- function loadFile(file) {
323
- if (!isReadableFile(file)) return;
324
- const result = dotenvConfig({ path: file, override: false });
325
- if (!result.error) loadedFiles.push(file);
326
- }
327
- function loadLegacyFile(file) {
328
- if (!isReadableFile(file)) return;
329
- let parsed;
330
- try {
331
- parsed = dotenvParse(fs3.readFileSync(file));
332
- } catch {
333
- return;
334
- }
335
- for (const [name, value] of Object.entries(parsed)) {
336
- if (isLegacyAllowedName(name) && process.env[name] === void 0) process.env[name] = value;
337
- }
338
- loadedFiles.push(file);
339
- }
340
- function loadEnv() {
341
- if (loaded) return;
342
- loaded = true;
343
- loadedFiles = [];
344
- const cwd = process.cwd();
345
- const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os.homedir();
346
- loadFile(path3.resolve(cwd, ".env"));
347
- if (home) loadFile(path3.join(home, ".multix", ".env"));
348
- if (legacyClaudeEnvEnabled()) {
349
- for (const file of legacyClaudeEnvFiles(cwd, home)) loadLegacyFile(file);
350
- }
351
- }
352
- function getLoadedEnvFiles() {
353
- return [...loadedFiles];
354
- }
355
- function resolveKey(name) {
356
- return process.env[name] || void 0;
2129
+ throw new PollTimeoutError2();
357
2130
  }
358
2131
 
359
2132
  // src/providers/byteplus/client.ts
360
- init_errors();
361
-
362
- // src/core/http-client.ts
363
- init_errors();
364
- import { once } from "events";
365
- import fs4 from "fs";
366
- import path4 from "path";
367
- import { pipeline } from "stream/promises";
368
-
369
- // src/core/fetch-json.ts
370
- init_errors();
371
- var DEFAULT_TIMEOUT_MS = 12e4;
372
- var SNIPPET_MAX = 500;
373
- function parseRetryAfter(value, now = Date.now()) {
374
- if (!value) return void 0;
375
- const trimmed = value.trim();
376
- if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1e3);
377
- const date = Date.parse(trimmed);
378
- if (Number.isNaN(date)) return void 0;
379
- return Math.max(0, date - now);
380
- }
381
- async function httpErrorFromResponse(response, url) {
382
- let snippet = "";
383
- try {
384
- const text = await response.text();
385
- snippet = text.slice(0, SNIPPET_MAX);
386
- } catch {
387
- snippet = "(could not read body)";
388
- }
389
- return new HttpError(
390
- response.status,
391
- snippet,
392
- url,
393
- parseRetryAfter(response.headers.get("retry-after"))
394
- );
395
- }
396
- async function fetchJson(opts) {
397
- const { url, method = "GET", headers: headers2 = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
398
- const doFetch = opts.fetch ?? ((input, init2) => globalThis.fetch(input, init2));
399
- const init = {
400
- method,
401
- headers: {
402
- "Content-Type": "application/json",
403
- ...headers2
404
- },
405
- signal: AbortSignal.timeout(timeoutMs)
406
- };
407
- if (body !== void 0) {
408
- init.body = JSON.stringify(body);
409
- }
410
- let response;
411
- try {
412
- response = await doFetch(url, init);
413
- } catch (cause) {
414
- const msg = cause instanceof Error ? cause.message : String(cause);
415
- throw new HttpError(0, msg, url);
416
- }
417
- if (!response.ok) {
418
- throw await httpErrorFromResponse(response, url);
419
- }
420
- let text;
421
- try {
422
- text = await response.text();
423
- } catch (cause) {
424
- const msg = cause instanceof Error ? cause.message : String(cause);
425
- throw new HttpError(0, `Failed to read response body: ${msg}`, url);
426
- }
427
- if (text.trim() === "") return void 0;
428
- try {
429
- return JSON.parse(text);
430
- } catch {
431
- throw new HttpError(
432
- response.status,
433
- `Invalid JSON response: ${text.slice(0, SNIPPET_MAX)}`,
434
- url
435
- );
436
- }
437
- }
438
-
439
- // src/core/http-client.ts
440
- async function httpJson(opts) {
441
- return fetchJson(opts);
442
- }
443
- async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS, headers2) {
444
- fs4.mkdirSync(path4.dirname(dest), { recursive: true });
445
- let response;
446
- try {
447
- response = await globalThis.fetch(url, {
448
- signal: AbortSignal.timeout(timeoutMs),
449
- ...headers2 ? { headers: headers2 } : {}
450
- });
451
- } catch (cause) {
452
- const msg = cause instanceof Error ? cause.message : String(cause);
453
- throw new HttpError(0, msg, url);
454
- }
455
- if (!response.ok) {
456
- throw new HttpError(response.status, "(download failed)", url);
457
- }
458
- if (!response.body) {
459
- throw new HttpError(0, "Response body is null", url);
460
- }
461
- const fileStream = fs4.createWriteStream(dest);
462
- try {
463
- await pipeline(response.body, fileStream);
464
- } catch (err2) {
465
- if (!fileStream.closed) await once(fileStream, "close");
466
- throw err2;
467
- }
468
- }
469
- async function fetchBytes(urlOrDataUri) {
470
- if (urlOrDataUri.startsWith("data:")) {
471
- const commaIdx = urlOrDataUri.indexOf(",");
472
- if (commaIdx === -1) throw new Error("Malformed data URI: missing comma");
473
- const encoded = urlOrDataUri.slice(commaIdx + 1);
474
- return Buffer.from(encoded, "base64");
475
- }
476
- let response;
477
- try {
478
- response = await globalThis.fetch(urlOrDataUri, {
479
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
480
- });
481
- } catch (cause) {
482
- const msg = cause instanceof Error ? cause.message : String(cause);
483
- throw new HttpError(0, msg, urlOrDataUri);
484
- }
485
- if (!response.ok) {
486
- throw new HttpError(response.status, "(bytes fetch failed)", urlOrDataUri);
487
- }
488
- return Buffer.from(await response.arrayBuffer());
489
- }
2133
+ init_errors();
490
2134
 
491
2135
  // src/providers/byteplus/models.ts
492
2136
  var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
@@ -531,9 +2175,9 @@ var BytePlusClient = class {
531
2175
  }
532
2176
  apiKey;
533
2177
  baseUrl;
534
- async request(path50, opts = {}) {
2178
+ async request(path52, opts = {}) {
535
2179
  const base = this.baseUrl.replace(/\/$/, "");
536
- const url = new URL(base + path50);
2180
+ const url = new URL(base + path52);
537
2181
  if (opts.query) {
538
2182
  for (const [k, v] of Object.entries(opts.query)) {
539
2183
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -551,14 +2195,14 @@ var BytePlusClient = class {
551
2195
  timeoutMs: opts.timeoutMs ?? 12e4
552
2196
  });
553
2197
  }
554
- get(path50, query, logger) {
555
- return this.request(path50, { method: "GET", query, logger });
2198
+ get(path52, query, logger) {
2199
+ return this.request(path52, { method: "GET", query, logger });
556
2200
  }
557
- post(path50, body, logger) {
558
- return this.request(path50, { method: "POST", body, logger });
2201
+ post(path52, body, logger) {
2202
+ return this.request(path52, { method: "POST", body, logger });
559
2203
  }
560
- delete(path50, logger) {
561
- return this.request(path50, { method: "DELETE", logger });
2204
+ delete(path52, logger) {
2205
+ return this.request(path52, { method: "DELETE", logger });
562
2206
  }
563
2207
  };
564
2208
  function createBytePlusClient() {
@@ -663,13 +2307,13 @@ function registerBytePlusGenerate3DCommand(parent) {
663
2307
  process.exit(1);
664
2308
  }
665
2309
  const ext = inferModelFileExt(url);
666
- const outPath = opts.output ?? path5.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
2310
+ const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
667
2311
  await downloadModelFile(url, outPath, logger);
668
2312
  console.log(`
669
2313
  Task: ${taskId}`);
670
2314
  console.log(`Saved: ${outPath}`);
671
2315
  } catch (e) {
672
- if (e instanceof PollTimeoutError) {
2316
+ if (e instanceof PollTimeoutError2) {
673
2317
  logger.error(
674
2318
  `Polling timed out \u2014 keep the task and check later: multix byteplus status ${taskId} --wait --download`
675
2319
  );
@@ -685,196 +2329,9 @@ Task: ${taskId}`);
685
2329
  );
686
2330
  }
687
2331
 
688
- // src/core/image-webp-finalize.ts
689
- init_errors();
690
- import { spawnSync } from "child_process";
691
- import fs6 from "fs";
692
- import path6 from "path";
693
- import { Option } from "commander";
694
-
695
- // src/core/image-header.ts
696
- import fs5 from "fs";
697
- function sniffImageKind(buf) {
698
- if (buf.length >= 8 && buf.readUInt32BE(0) === 2303741511) return "png";
699
- if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "jpeg";
700
- if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP")
701
- return "webp";
702
- return void 0;
703
- }
704
- function readPng(buf) {
705
- if (buf.length < 33) return void 0;
706
- const width = buf.readUInt32BE(16);
707
- const height = buf.readUInt32BE(20);
708
- const colorType = buf[25];
709
- let hasAlpha = colorType === 4 || colorType === 6;
710
- let offset = 8;
711
- while (!hasAlpha && offset + 8 <= buf.length) {
712
- const len = buf.readUInt32BE(offset);
713
- const type = buf.toString("ascii", offset + 4, offset + 8);
714
- if (type === "tRNS") hasAlpha = true;
715
- if (type === "IDAT" || type === "IEND") break;
716
- offset += 12 + len;
717
- }
718
- return { kind: "png", width, height, hasAlpha };
719
- }
720
- function readJpeg(buf) {
721
- let offset = 2;
722
- while (offset + 9 < buf.length) {
723
- if (buf[offset] !== 255) {
724
- offset++;
725
- continue;
726
- }
727
- const marker = buf[offset + 1] ?? 0;
728
- if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
729
- return {
730
- kind: "jpeg",
731
- height: buf.readUInt16BE(offset + 5),
732
- width: buf.readUInt16BE(offset + 7),
733
- hasAlpha: false
734
- };
735
- }
736
- if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
737
- offset += 2;
738
- continue;
739
- }
740
- offset += 2 + buf.readUInt16BE(offset + 2);
741
- }
742
- return void 0;
743
- }
744
- function readWebp(buf) {
745
- if (buf.length < 30) return void 0;
746
- const chunk = buf.toString("ascii", 12, 16);
747
- if (chunk === "VP8X") {
748
- const flags = buf[20] ?? 0;
749
- return {
750
- kind: "webp",
751
- width: 1 + buf.readUIntLE(24, 3),
752
- height: 1 + buf.readUIntLE(27, 3),
753
- hasAlpha: (flags & 16) !== 0
754
- };
755
- }
756
- if (chunk === "VP8L") {
757
- const bits = buf.readUInt32LE(21);
758
- return {
759
- kind: "webp",
760
- width: 1 + (bits & 16383),
761
- height: 1 + (bits >>> 14 & 16383),
762
- hasAlpha: (bits >>> 28 & 1) === 1
763
- };
764
- }
765
- if (chunk === "VP8 ") {
766
- return {
767
- kind: "webp",
768
- width: buf.readUInt16LE(26) & 16383,
769
- height: buf.readUInt16LE(28) & 16383,
770
- hasAlpha: false
771
- };
772
- }
773
- return void 0;
774
- }
775
- function readImageHeader(buf) {
776
- const kind = sniffImageKind(buf);
777
- if (kind === "png") return readPng(buf);
778
- if (kind === "jpeg") return readJpeg(buf);
779
- if (kind === "webp") return readWebp(buf);
780
- return void 0;
781
- }
782
- function readImageHeaderFromFile(file) {
783
- return readImageHeader(fs5.readFileSync(file));
784
- }
785
-
786
- // src/core/image-webp-finalize.ts
787
- var CWEBP_ARGS = ["-q", "85", "-m", "6", "-metadata", "none"];
788
- var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tif", ".tiff"]);
789
- function imageFormatOption() {
790
- return new Option(
791
- "--image-format <mode>",
792
- "Final image format: webp (default, cwebp -q 85) | original (keep provider format/quality)"
793
- );
794
- }
795
- function noWebpOption() {
796
- return new Option("--no-webp", "Keep the provider's original image format and quality");
797
- }
798
- function resolveImageFormatMode(opts, explicitProviderFormat = false) {
799
- if (opts.webp === false && opts.imageFormat === void 0) return "original";
800
- const raw = opts.imageFormat ?? process.env.MULTIX_IMAGE_FORMAT;
801
- if (raw !== void 0 && raw !== "") {
802
- const mode = raw.toLowerCase();
803
- if (mode !== "webp" && mode !== "original") {
804
- throw new ValidationError(`Invalid --image-format: ${raw}. Valid: webp, original`);
805
- }
806
- return mode;
807
- }
808
- if (explicitProviderFormat) return "original";
809
- const ext = opts.output ? path6.extname(opts.output).toLowerCase() : "";
810
- if (ext && ext !== ".webp" && IMAGE_EXTS.has(ext)) return "original";
811
- return "webp";
812
- }
813
- function hasCwebp() {
814
- const res = spawnSync("cwebp", ["-version"], { stdio: "ignore" });
815
- return !res.error && res.status === 0;
816
- }
817
- function webpTarget(file) {
818
- const ext = path6.extname(file);
819
- return ext.toLowerCase() === ".webp" ? file : `${file.slice(0, -ext.length || void 0)}.webp`;
820
- }
821
- function convertToWebp(file, logger, keepPath = false) {
822
- const src = readImageHeaderFromFile(file);
823
- if (!src) {
824
- logger?.warn(`Skipping WebP conversion (unrecognized image): ${file}`);
825
- return file;
826
- }
827
- if (src.kind === "webp") {
828
- const target2 = keepPath ? file : webpTarget(file);
829
- if (target2 !== file) fs6.renameSync(file, target2);
830
- return target2;
831
- }
832
- const target = keepPath ? file : webpTarget(file);
833
- const tmp = `${target}.tmp-${process.pid}.webp`;
834
- const res = spawnSync("cwebp", [...CWEBP_ARGS, file, "-o", tmp], { encoding: "utf8" });
835
- if (res.error || res.status !== 0) {
836
- fs6.rmSync(tmp, { force: true });
837
- logger?.warn(`cwebp failed, keeping original ${file}: ${res.stderr || res.error?.message}`);
838
- return file;
839
- }
840
- const out = fs6.existsSync(tmp) ? readImageHeaderFromFile(tmp) : void 0;
841
- const problem = !out ? "output is not a valid WebP" : out.width !== src.width || out.height !== src.height ? `size changed ${src.width}x${src.height} -> ${out.width}x${out.height}` : src.hasAlpha && !out.hasAlpha ? "transparency lost" : void 0;
842
- if (problem) {
843
- fs6.rmSync(tmp, { force: true });
844
- logger?.warn(`WebP verification failed (${problem}), keeping original ${file}`);
845
- return file;
846
- }
847
- if (target !== file) fs6.rmSync(file, { force: true });
848
- fs6.renameSync(tmp, target);
849
- logger?.debug(`WebP verified ${out?.width}x${out?.height} alpha=${out?.hasAlpha}: ${target}`);
850
- return target;
851
- }
852
- function finalizeGeneratedImages(files, opts) {
853
- const mode = resolveImageFormatMode(opts, opts.explicitProviderFormat);
854
- if (mode === "original" || files.length === 0) return files;
855
- if (!hasCwebp()) {
856
- opts.logger?.warn(
857
- "cwebp not found on PATH; keeping original image format (install libwebp, or pass --image-format original)"
858
- );
859
- return files;
860
- }
861
- const outputAbs = opts.output ? path6.resolve(opts.output) : void 0;
862
- const wroteOutputDirectly = outputAbs !== void 0 && files.some((f) => path6.resolve(f) === outputAbs);
863
- const finals = files.map(
864
- (f) => convertToWebp(f, opts.logger, outputAbs !== void 0 && path6.resolve(f) === outputAbs)
865
- );
866
- if (opts.output && outputAbs && !wroteOutputDirectly && finals[0] && fs6.existsSync(outputAbs)) {
867
- const outExt = path6.extname(outputAbs).toLowerCase();
868
- if ((outExt === ".webp" || outExt === "") && path6.extname(finals[0]).toLowerCase() === ".webp") {
869
- fs6.copyFileSync(finals[0], outputAbs);
870
- }
871
- }
872
- return finals;
873
- }
874
-
875
2332
  // src/providers/byteplus/generators/image.ts
876
- import fs7 from "fs";
877
- import path7 from "path";
2333
+ import fs11 from "fs";
2334
+ import path10 from "path";
878
2335
  var ASPECT_TO_SIZE = {
879
2336
  "1:1": "2048x2048",
880
2337
  "16:9": "2560x1440",
@@ -938,14 +2395,14 @@ async function generateSeedream(client, opts) {
938
2395
  }
939
2396
  const ext = (() => {
940
2397
  try {
941
- const e = path7.extname(new URL(url).pathname);
2398
+ const e = path10.extname(new URL(url).pathname);
942
2399
  return e || ".jpg";
943
2400
  } catch {
944
2401
  return ".jpg";
945
2402
  }
946
2403
  })();
947
- const filename = opts.output && items.length === 1 ? path7.resolve(opts.output) : path7.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
948
- fs7.mkdirSync(path7.dirname(filename), { recursive: true });
2404
+ const filename = opts.output && items.length === 1 ? path10.resolve(opts.output) : path10.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
2405
+ fs11.mkdirSync(path10.dirname(filename), { recursive: true });
949
2406
  await downloadFile(url, filename);
950
2407
  saved.push({ path: filename, url, seed: item.seed });
951
2408
  logger?.success(`Saved ${filename}`);
@@ -1028,97 +2485,13 @@ Generated ${finalImages.length} image(s):`);
1028
2485
  }
1029
2486
  );
1030
2487
  }
1031
- function collect2(value, prev) {
1032
- return [...prev, value];
1033
- }
1034
-
1035
- // src/providers/byteplus/commands/image-to-video.ts
1036
- import path9 from "path";
1037
-
1038
- // src/core/video-thumb.ts
1039
- import fs8 from "fs";
1040
- import path8 from "path";
1041
- var THUMB_KEYS = /* @__PURE__ */ new Set([
1042
- "cover_image_url",
1043
- "coverImageUrl",
1044
- "cover_url",
1045
- "coverUrl",
1046
- "thumbnail_url",
1047
- "thumbnailUrl",
1048
- "thumb_url",
1049
- "thumbUrl",
1050
- "preview_url",
1051
- "previewUrl",
1052
- "first_frame_url",
1053
- "firstFrameUrl",
1054
- "poster_url",
1055
- "posterUrl",
1056
- "image_url",
1057
- "imageUrl"
1058
- ]);
1059
- var IMAGE_EXT_RE = /\.(jpe?g|png|webp|gif|bmp)(\?|$)/i;
1060
- function isImageUrl(value) {
1061
- if (typeof value !== "string") return false;
1062
- if (!/^https?:\/\//i.test(value)) return false;
1063
- return IMAGE_EXT_RE.test(value);
1064
- }
1065
- function detectThumbUrl(obj) {
1066
- if (obj === null || obj === void 0) return null;
1067
- if (Array.isArray(obj)) {
1068
- for (const item of obj) {
1069
- const found = detectThumbUrl(item);
1070
- if (found) return found;
1071
- }
1072
- return null;
1073
- }
1074
- if (typeof obj !== "object") return null;
1075
- const record = obj;
1076
- for (const [key, value] of Object.entries(record)) {
1077
- if (THUMB_KEYS.has(key) && isImageUrl(value)) return value;
1078
- }
1079
- for (const value of Object.values(record)) {
1080
- if (value && typeof value === "object") {
1081
- const found = detectThumbUrl(value);
1082
- if (found) return found;
1083
- }
1084
- }
1085
- return null;
1086
- }
1087
- async function downloadThumbBeside(thumbUrl, videoPath, logger) {
1088
- const match = thumbUrl.match(IMAGE_EXT_RE);
1089
- const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
1090
- const dir = path8.dirname(videoPath);
1091
- const base = path8.basename(videoPath, path8.extname(videoPath));
1092
- const dest = path8.join(dir, `${base}_thumb.${ext}`);
1093
- try {
1094
- await downloadFile(thumbUrl, dest);
1095
- logger?.success(`Saved thumb ${dest}`);
1096
- return dest;
1097
- } catch (e) {
1098
- logger?.warn(`Thumb download failed: ${e instanceof Error ? e.message : String(e)}`);
1099
- return null;
1100
- }
1101
- }
1102
- async function maybeDownloadThumb(source, videoPath, opts = {}) {
1103
- if (opts.skip) return null;
1104
- const url = detectThumbUrl(source);
1105
- if (!url) {
1106
- opts.logger?.debug("No thumbnail URL detected in provider response");
1107
- return null;
1108
- }
1109
- const saved = await downloadThumbBeside(url, videoPath, opts.logger);
1110
- if (saved && opts.copyTo) {
1111
- const copyExt = path8.extname(saved);
1112
- const copyBase = path8.basename(opts.copyTo, path8.extname(opts.copyTo));
1113
- const copyDir = path8.dirname(path8.resolve(opts.copyTo));
1114
- const copyDest = path8.join(copyDir, `${copyBase}_thumb${copyExt}`);
1115
- fs8.mkdirSync(copyDir, { recursive: true });
1116
- fs8.copyFileSync(saved, copyDest);
1117
- opts.logger?.success(`Copied thumb to ${copyDest}`);
1118
- }
1119
- return saved;
2488
+ function collect2(value, prev) {
2489
+ return [...prev, value];
1120
2490
  }
1121
2491
 
2492
+ // src/providers/byteplus/commands/image-to-video.ts
2493
+ import path11 from "path";
2494
+
1122
2495
  // src/providers/byteplus/generators/video.ts
1123
2496
  function buildFlagSuffix(opts) {
1124
2497
  const parts = [];
@@ -1240,7 +2613,7 @@ function registerBytePlusImageToVideoCommand(parent) {
1240
2613
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
1241
2614
  process.exit(1);
1242
2615
  }
1243
- const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
2616
+ const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1244
2617
  await downloadVideo(url, outPath, logger);
1245
2618
  await maybeDownloadThumb(final, outPath, {
1246
2619
  skip: opts.thumb === false,
@@ -1251,7 +2624,7 @@ function registerBytePlusImageToVideoCommand(parent) {
1251
2624
  Task: ${taskId}`);
1252
2625
  console.log(`Saved: ${outPath}`);
1253
2626
  } catch (e) {
1254
- if (e instanceof PollTimeoutError) {
2627
+ if (e instanceof PollTimeoutError2) {
1255
2628
  logger.error(`Timed out \u2014 multix byteplus status ${taskId}`);
1256
2629
  process.exit(1);
1257
2630
  }
@@ -1266,7 +2639,7 @@ Task: ${taskId}`);
1266
2639
  }
1267
2640
 
1268
2641
  // src/providers/byteplus/commands/reference-to-video.ts
1269
- import path10 from "path";
2642
+ import path12 from "path";
1270
2643
 
1271
2644
  // src/providers/byteplus/reference-input.ts
1272
2645
  var MAX_TOTAL_REFS = 12;
@@ -1298,10 +2671,10 @@ function toRef(kind, resolved, role) {
1298
2671
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
1299
2672
  }
1300
2673
  async function resolveRefSpec(kind, spec, logger) {
1301
- const { path: path50, role } = parseRefSpec(spec);
1302
- if (kind === "image") return toRef(kind, await resolveImageInput(path50, logger), role);
1303
- if (kind === "video") return toRef(kind, await resolveVideoInput(path50, logger), role);
1304
- return toRef(kind, await resolveAudioInput(path50, logger), role);
2674
+ const { path: path52, role } = parseRefSpec(spec);
2675
+ if (kind === "image") return toRef(kind, await resolveImageInput(path52, logger), role);
2676
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path52, logger), role);
2677
+ return toRef(kind, await resolveAudioInput(path52, logger), role);
1305
2678
  }
1306
2679
  async function resolveRefSpecs(specs, logger) {
1307
2680
  const out = [];
@@ -1398,7 +2771,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
1398
2771
  logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
1399
2772
  process.exit(1);
1400
2773
  }
1401
- const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
2774
+ const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1402
2775
  await downloadVideo(url, outPath, logger);
1403
2776
  await maybeDownloadThumb(final, outPath, {
1404
2777
  skip: opts.thumb === false,
@@ -1409,7 +2782,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
1409
2782
  Task: ${taskId}`);
1410
2783
  console.log(`Saved: ${outPath}`);
1411
2784
  } catch (e) {
1412
- if (e instanceof PollTimeoutError) {
2785
+ if (e instanceof PollTimeoutError2) {
1413
2786
  logger.error(`Timed out \u2014 multix byteplus status ${taskId}`);
1414
2787
  process.exit(1);
1415
2788
  }
@@ -1424,7 +2797,7 @@ Task: ${taskId}`);
1424
2797
  }
1425
2798
 
1426
2799
  // src/providers/byteplus/commands/status.ts
1427
- import path11 from "path";
2800
+ import path13 from "path";
1428
2801
  function registerBytePlusStatusCommand(parent) {
1429
2802
  parent.command("status <taskId>").description("Get a Seedance task's status; --wait + --download to fetch the result MP4").option("--wait", "Poll until terminal status").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download MP4 on success (implies --wait)").option("--output <path>", "Output MP4 path (with --download)").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
1430
2803
  async (taskId, opts) => {
@@ -1442,7 +2815,7 @@ function registerBytePlusStatusCommand(parent) {
1442
2815
  const videoUrl2 = content?.video_url;
1443
2816
  const fileUrl = content?.file_url;
1444
2817
  if (videoUrl2) {
1445
- const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
2818
+ const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
1446
2819
  await downloadVideo(videoUrl2, outPath, logger);
1447
2820
  await maybeDownloadThumb(status, outPath, {
1448
2821
  skip: opts.thumb === false,
@@ -1452,7 +2825,7 @@ function registerBytePlusStatusCommand(parent) {
1452
2825
  console.log(`Saved: ${outPath}`);
1453
2826
  } else if (fileUrl) {
1454
2827
  const ext = inferModelFileExt(fileUrl);
1455
- const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
2828
+ const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
1456
2829
  await downloadModelFile(fileUrl, outPath, logger);
1457
2830
  console.log(`Saved: ${outPath}`);
1458
2831
  } else {
@@ -1461,7 +2834,7 @@ function registerBytePlusStatusCommand(parent) {
1461
2834
  }
1462
2835
  }
1463
2836
  } catch (e) {
1464
- if (e instanceof PollTimeoutError) {
2837
+ if (e instanceof PollTimeoutError2) {
1465
2838
  logger.error(`Timed out waiting for task ${taskId}`);
1466
2839
  process.exit(1);
1467
2840
  }
@@ -1476,7 +2849,7 @@ function registerBytePlusStatusCommand(parent) {
1476
2849
  }
1477
2850
 
1478
2851
  // src/providers/byteplus/commands/video.ts
1479
- import path12 from "path";
2852
+ import path14 from "path";
1480
2853
  function registerBytePlusVideoCommand(parent) {
1481
2854
  parent.command("video").description("Generate a video from a text prompt via BytePlus Seedance 2.0").requiredOption("--prompt <text>", "Text prompt").option("-m, --model <id>", "Model id", BYTEPLUS_DEFAULTS.videoModel).option("--resolution <r>", "480p|720p|1080p|2k", BYTEPLUS_DEFAULTS.videoResolution).option("--duration <n>", "Duration seconds (4-15)", String(BYTEPLUS_DEFAULTS.videoDuration)).option(
1482
2855
  "--aspect-ratio <r>",
@@ -1518,7 +2891,7 @@ function registerBytePlusVideoCommand(parent) {
1518
2891
  logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
1519
2892
  process.exit(1);
1520
2893
  }
1521
- const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
2894
+ const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
1522
2895
  await downloadVideo(url, outPath, logger);
1523
2896
  await maybeDownloadThumb(final, outPath, {
1524
2897
  skip: opts.thumb === false,
@@ -1529,7 +2902,7 @@ function registerBytePlusVideoCommand(parent) {
1529
2902
  Task: ${taskId}`);
1530
2903
  console.log(`Saved: ${outPath}`);
1531
2904
  } catch (e) {
1532
- if (e instanceof PollTimeoutError) {
2905
+ if (e instanceof PollTimeoutError2) {
1533
2906
  logger.error(
1534
2907
  `Polling timed out \u2014 try --async to keep the task and check later: multix byteplus status ${taskId}`
1535
2908
  );
@@ -1736,15 +3109,15 @@ async function downloadVideo2(url) {
1736
3109
 
1737
3110
  // src/providers/cloudflare/media-output.ts
1738
3111
  init_errors();
1739
- import fs9 from "fs";
1740
- import path13 from "path";
3112
+ import fs12 from "fs";
3113
+ import path15 from "path";
1741
3114
  function destination(filename, output) {
1742
- return output ? path13.resolve(output) : path13.join(getOutputDir(), filename);
3115
+ return output ? path15.resolve(output) : path15.join(getOutputDir(), filename);
1743
3116
  }
1744
3117
  function saveBytes(bytes, filename, output) {
1745
3118
  const dest = destination(filename, output);
1746
- fs9.mkdirSync(path13.dirname(dest), { recursive: true });
1747
- fs9.writeFileSync(dest, bytes);
3119
+ fs12.mkdirSync(path15.dirname(dest), { recursive: true });
3120
+ fs12.writeFileSync(dest, bytes);
1748
3121
  return dest;
1749
3122
  }
1750
3123
  function saveBase64Image(image, output) {
@@ -1927,8 +3300,8 @@ function registerCloudflareCommands(program2) {
1927
3300
  }
1928
3301
 
1929
3302
  // src/providers/elevenlabs/client.ts
1930
- import fs10 from "fs";
1931
- import path14 from "path";
3303
+ import fs13 from "fs";
3304
+ import path16 from "path";
1932
3305
  init_errors();
1933
3306
 
1934
3307
  // src/providers/elevenlabs/models.ts
@@ -2089,13 +3462,13 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
2089
3462
  return res.json();
2090
3463
  }
2091
3464
  function readFileAsBlob(filePath, fieldFilename) {
2092
- const abs = path14.resolve(filePath);
2093
- if (!fs10.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
2094
- const buf = fs10.readFileSync(abs);
2095
- const name = fieldFilename ?? path14.basename(abs);
3465
+ const abs = path16.resolve(filePath);
3466
+ if (!fs13.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
3467
+ const buf = fs13.readFileSync(abs);
3468
+ const name = fieldFilename ?? path16.basename(abs);
2096
3469
  return new File([new Uint8Array(buf)], name);
2097
3470
  }
2098
- async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
3471
+ async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
2099
3472
  const { intervalMs = 5e3, timeoutMs = 6e5, logger, label = "task" } = opts;
2100
3473
  const deadline = Date.now() + timeoutMs;
2101
3474
  let elapsed = 0;
@@ -2116,12 +3489,12 @@ async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
2116
3489
  function saveBytes2(opts) {
2117
3490
  const { bytes, task, ext, outputCopy, logger } = opts;
2118
3491
  const dir = getOutputDir();
2119
- const dest = path14.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
2120
- fs10.writeFileSync(dest, bytes);
3492
+ const dest = path16.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
3493
+ fs13.writeFileSync(dest, bytes);
2121
3494
  logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
2122
3495
  if (outputCopy) {
2123
- fs10.mkdirSync(path14.dirname(path14.resolve(outputCopy)), { recursive: true });
2124
- fs10.copyFileSync(dest, outputCopy);
3496
+ fs13.mkdirSync(path16.dirname(path16.resolve(outputCopy)), { recursive: true });
3497
+ fs13.copyFileSync(dest, outputCopy);
2125
3498
  }
2126
3499
  return dest;
2127
3500
  }
@@ -2160,14 +3533,14 @@ ${models.length} models`);
2160
3533
  }
2161
3534
 
2162
3535
  // src/providers/elevenlabs/commands/align.ts
2163
- import fs11 from "fs";
2164
- import path15 from "path";
3536
+ import fs14 from "fs";
3537
+ import path17 from "path";
2165
3538
  function registerAlignCommand(parent) {
2166
3539
  parent.command("align").description("Forced alignment of audio to a transcript (returns word/char timestamps)").requiredOption("--input <path>", "Source audio file").requiredOption("--text <str>", "Transcript text (or pass --text-file)").option("--text-file <path>", "Read transcript from file").option("--output <path>", "Save JSON to this path (default: multix-output)").option("-v, --verbose", "Verbose logging").action(async (opts) => {
2167
3540
  const logger = createLogger({ verbose: opts.verbose ?? false });
2168
3541
  const apiKey = requireElevenLabsKey();
2169
3542
  let text = opts.text;
2170
- if (opts.textFile) text = fs11.readFileSync(path15.resolve(opts.textFile), "utf8");
3543
+ if (opts.textFile) text = fs14.readFileSync(path17.resolve(opts.textFile), "utf8");
2171
3544
  const form = new FormData();
2172
3545
  form.append("file", readFileAsBlob(opts.input));
2173
3546
  form.append("text", text);
@@ -2175,9 +3548,9 @@ function registerAlignCommand(parent) {
2175
3548
  const data = await apiPostMultipart("forced-alignment", form, apiKey, {
2176
3549
  timeoutMs: 6e5
2177
3550
  });
2178
- const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
2179
- fs11.mkdirSync(path15.dirname(dest), { recursive: true });
2180
- fs11.writeFileSync(dest, JSON.stringify(data, null, 2));
3551
+ const dest = opts.output ? path17.resolve(opts.output) : path17.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
3552
+ fs14.mkdirSync(path17.dirname(dest), { recursive: true });
3553
+ fs14.writeFileSync(dest, JSON.stringify(data, null, 2));
2181
3554
  logger.success(`Saved alignment: ${dest}`);
2182
3555
  if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
2183
3556
  });
@@ -2219,8 +3592,8 @@ function registerCloneCommand(parent) {
2219
3592
 
2220
3593
  // src/providers/elevenlabs/commands/dub.ts
2221
3594
  init_errors();
2222
- import fs12 from "fs";
2223
- import path16 from "path";
3595
+ import fs15 from "fs";
3596
+ import path18 from "path";
2224
3597
  function registerDubCommand(parent) {
2225
3598
  parent.command("dub").description("Dub a video/audio file into target languages (async)").option("--input <path>", "Source media file (use --input or --source-url)").option("--source-url <url>", "Public URL of source media").requiredOption("--target-lang <code>", "Target language ISO code (e.g. es, fr, de)").option("--source-lang <code>", "Source language (auto if omitted)").option("--num-speakers <n>", "Speaker count (auto if omitted)").option("--watermark", "Add ElevenLabs watermark", false).option("--start-time <s>", "Start time in seconds").option("--end-time <s>", "End time in seconds").option("--highest-resolution", "Use highest resolution video", false).option("--name <n>", "Project display name").option("--async", "Return job id immediately, do not wait", false).option("--wait", "Wait until status=dubbed (implied by --download)", false).option("--download", "Download dubbed media when ready", false).option("--poll-interval <s>", "Poll interval seconds", "10").option("--wait-timeout <ms>", "Poll timeout", "1800000").option("--output <path>", "Output path for downloaded dub").option("-v, --verbose", "Verbose logging").action(async (opts) => {
2226
3599
  const logger = createLogger({ verbose: opts.verbose ?? false });
@@ -2250,7 +3623,7 @@ function registerDubCommand(parent) {
2250
3623
  console.log("Run `multix elevenlabs dub-status <id>` to check progress.");
2251
3624
  return;
2252
3625
  }
2253
- const final = await pollUntil(
3626
+ const final = await pollUntil2(
2254
3627
  () => apiGet(`dubbing/${start.dubbing_id}`, apiKey),
2255
3628
  (s) => s.status === "dubbed",
2256
3629
  (s) => s.status === "failed",
@@ -2263,7 +3636,7 @@ function registerDubCommand(parent) {
2263
3636
  );
2264
3637
  logger.success(`Dub complete: ${start.dubbing_id}`);
2265
3638
  if (opts.download) {
2266
- const dest = opts.output ? path16.resolve(opts.output) : path16.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
3639
+ const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
2267
3640
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
2268
3641
  await downloadDub(url, apiKey, dest);
2269
3642
  logger.success(`Saved: ${dest}`);
@@ -2278,7 +3651,7 @@ Dubbed file: ${dest}`);
2278
3651
  const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
2279
3652
  console.log(JSON.stringify(status, null, 2));
2280
3653
  if (opts.download) {
2281
- const dest = opts.output ? path16.resolve(opts.output) : path16.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
3654
+ const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
2282
3655
  const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
2283
3656
  await downloadDub(url, apiKey, dest);
2284
3657
  console.log(`Saved: ${dest}`);
@@ -2286,7 +3659,7 @@ Dubbed file: ${dest}`);
2286
3659
  });
2287
3660
  }
2288
3661
  async function downloadDub(url, apiKey, dest) {
2289
- fs12.mkdirSync(path16.dirname(dest), { recursive: true });
3662
+ fs15.mkdirSync(path18.dirname(dest), { recursive: true });
2290
3663
  const res = await globalThis.fetch(url, {
2291
3664
  headers: { "xi-api-key": apiKey },
2292
3665
  signal: AbortSignal.timeout(6e5)
@@ -2295,7 +3668,7 @@ async function downloadDub(url, apiKey, dest) {
2295
3668
  throw new Error(`Failed to download dub: HTTP ${res.status}`);
2296
3669
  }
2297
3670
  const arr = new Uint8Array(await res.arrayBuffer());
2298
- fs12.writeFileSync(dest, arr);
3671
+ fs15.writeFileSync(dest, arr);
2299
3672
  void downloadFile;
2300
3673
  }
2301
3674
 
@@ -2324,8 +3697,8 @@ Isolated audio: ${dest}`);
2324
3697
 
2325
3698
  // src/providers/elevenlabs/commands/music.ts
2326
3699
  init_errors();
2327
- import fs13 from "fs";
2328
- import path17 from "path";
3700
+ import fs16 from "fs";
3701
+ import path19 from "path";
2329
3702
  function registerMusicCommand(parent) {
2330
3703
  parent.command("music").description("Generate music from a text prompt or composition plan").option("--prompt <str>", "Music prompt (mutually exclusive with --plan)").option("--plan <path>", "Path to composition plan JSON file").option("--music-length-ms <n>", "Total length in milliseconds (default 30000)").option("--model <id>", "Music model", "music_v1").option(
2331
3704
  "--format <fmt>",
@@ -2340,9 +3713,9 @@ function registerMusicCommand(parent) {
2340
3713
  const payload = { model_id: opts.model };
2341
3714
  if (opts.prompt) payload.prompt = opts.prompt;
2342
3715
  if (opts.plan) {
2343
- const planPath = path17.resolve(opts.plan);
2344
- if (!fs13.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
2345
- const planJson = JSON.parse(fs13.readFileSync(planPath, "utf8"));
3716
+ const planPath = path19.resolve(opts.plan);
3717
+ if (!fs16.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
3718
+ const planJson = JSON.parse(fs16.readFileSync(planPath, "utf8"));
2346
3719
  payload.composition_plan = planJson;
2347
3720
  }
2348
3721
  if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
@@ -2401,8 +3774,8 @@ Generated SFX: ${dest}`);
2401
3774
  }
2402
3775
 
2403
3776
  // src/providers/elevenlabs/commands/transcribe.ts
2404
- import fs14 from "fs";
2405
- import path18 from "path";
3777
+ import fs17 from "fs";
3778
+ import path20 from "path";
2406
3779
  function registerTranscribeCommand(parent) {
2407
3780
  parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
2408
3781
  "--model <id>",
@@ -2425,7 +3798,7 @@ function registerTranscribeCommand(parent) {
2425
3798
  });
2426
3799
  const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
2427
3800
  const dir = getOutputDir();
2428
- const dest = opts.output ? path18.resolve(opts.output) : path18.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
3801
+ const dest = opts.output ? path20.resolve(opts.output) : path20.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
2429
3802
  let body;
2430
3803
  switch (opts.format) {
2431
3804
  case "json":
@@ -2440,8 +3813,8 @@ function registerTranscribeCommand(parent) {
2440
3813
  default:
2441
3814
  body = data.text ?? "";
2442
3815
  }
2443
- fs14.mkdirSync(path18.dirname(dest), { recursive: true });
2444
- fs14.writeFileSync(dest, body);
3816
+ fs17.mkdirSync(path20.dirname(dest), { recursive: true });
3817
+ fs17.writeFileSync(dest, body);
2445
3818
  logger.success(`Saved transcript: ${dest}`);
2446
3819
  if (data.language_code) {
2447
3820
  logger.info(
@@ -2687,8 +4060,8 @@ function registerElevenLabsCommands(program2) {
2687
4060
  }
2688
4061
 
2689
4062
  // src/providers/fal/commands/image.ts
2690
- import fs17 from "fs";
2691
- import path20 from "path";
4063
+ import fs19 from "fs";
4064
+ import path22 from "path";
2692
4065
 
2693
4066
  // src/providers/fal/models.ts
2694
4067
  var DEFAULT_FAL_BASE_URL = "https://queue.fal.run";
@@ -2702,37 +4075,8 @@ var FAL_DEFAULTS = {
2702
4075
  };
2703
4076
 
2704
4077
  // src/providers/fal/run-helpers.ts
2705
- import fs16 from "fs";
2706
- import path19 from "path";
2707
-
2708
- // src/core/json-input.ts
2709
- init_errors();
2710
- import fs15 from "fs";
2711
- function parseJsonInput(raw, flag = "--input") {
2712
- let text;
2713
- if (raw.startsWith("@")) {
2714
- try {
2715
- text = fs15.readFileSync(raw.slice(1), "utf8");
2716
- } catch (e) {
2717
- const msg = e instanceof Error ? e.message : String(e);
2718
- throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
2719
- }
2720
- } else {
2721
- text = raw;
2722
- }
2723
- try {
2724
- const parsed = JSON.parse(text);
2725
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2726
- throw new Error("input must be a JSON object");
2727
- }
2728
- return parsed;
2729
- } catch (e) {
2730
- const msg = e instanceof Error ? e.message : String(e);
2731
- throw new ValidationError(`Invalid ${flag} JSON: ${msg}`);
2732
- }
2733
- }
2734
-
2735
- // src/providers/fal/run-helpers.ts
4078
+ import fs18 from "fs";
4079
+ import path21 from "path";
2736
4080
  init_redact();
2737
4081
 
2738
4082
  // src/providers/fal/client.ts
@@ -2757,9 +4101,9 @@ var FalClient = class {
2757
4101
  apiKey;
2758
4102
  baseUrl;
2759
4103
  /** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
2760
- async request(path50, opts = {}) {
4104
+ async request(path52, opts = {}) {
2761
4105
  const base = this.baseUrl.replace(/\/$/, "");
2762
- const suffix = path50.startsWith("/") ? path50 : `/${path50}`;
4106
+ const suffix = path52.startsWith("/") ? path52 : `/${path52}`;
2763
4107
  return this.requestAbsolute(base + suffix, opts);
2764
4108
  }
2765
4109
  /** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
@@ -2776,11 +4120,11 @@ var FalClient = class {
2776
4120
  timeoutMs: opts.timeoutMs ?? 12e4
2777
4121
  });
2778
4122
  }
2779
- get(path50, logger) {
2780
- return this.request(path50, { method: "GET", logger });
4123
+ get(path52, logger) {
4124
+ return this.request(path52, { method: "GET", logger });
2781
4125
  }
2782
- post(path50, body, logger) {
2783
- return this.request(path50, { method: "POST", body, logger });
4126
+ post(path52, body, logger) {
4127
+ return this.request(path52, { method: "POST", body, logger });
2784
4128
  }
2785
4129
  getAbsolute(url, logger) {
2786
4130
  return this.requestAbsolute(url, { method: "GET", logger });
@@ -2922,7 +4266,7 @@ function extractMediaUrls(result) {
2922
4266
  function extFromUrl(url, fallback = ".bin") {
2923
4267
  try {
2924
4268
  const u = new URL(url);
2925
- const e = path19.extname(u.pathname);
4269
+ const e = path21.extname(u.pathname);
2926
4270
  return e.length > 0 ? e : fallback;
2927
4271
  } catch {
2928
4272
  return fallback;
@@ -2935,13 +4279,13 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
2935
4279
  if (!url) continue;
2936
4280
  const ext = extFromUrl(url);
2937
4281
  const filename = `fal-${requestId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
2938
- const filepath = path19.join(outDir, filename);
4282
+ const filepath = path21.join(outDir, filename);
2939
4283
  try {
2940
4284
  await downloadFile(url, filepath);
2941
4285
  saved.push(filepath);
2942
4286
  logger.success(`Saved ${filepath}`);
2943
4287
  } catch (e) {
2944
- fs16.rmSync(filepath, { force: true });
4288
+ fs18.rmSync(filepath, { force: true });
2945
4289
  logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
2946
4290
  }
2947
4291
  }
@@ -2986,9 +4330,9 @@ function registerFalImageCommand(parent) {
2986
4330
  process.exit(1);
2987
4331
  }
2988
4332
  if (opts.output && saved[0]) {
2989
- const dest = path20.resolve(opts.output);
2990
- fs17.mkdirSync(path20.dirname(dest), { recursive: true });
2991
- fs17.copyFileSync(saved[0], dest);
4333
+ const dest = path22.resolve(opts.output);
4334
+ fs19.mkdirSync(path22.dirname(dest), { recursive: true });
4335
+ fs19.copyFileSync(saved[0], dest);
2992
4336
  logger.success(`Copied to ${dest}`);
2993
4337
  }
2994
4338
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -3143,9 +4487,9 @@ function registerFalCommands(program2) {
3143
4487
 
3144
4488
  // src/core/stdin-file.ts
3145
4489
  init_errors();
3146
- import fs18 from "fs";
4490
+ import fs20 from "fs";
3147
4491
  import os2 from "os";
3148
- import path21 from "path";
4492
+ import path23 from "path";
3149
4493
  var STDIN_ARG = "-";
3150
4494
  var STDIN_LABEL = "<stdin>";
3151
4495
  var HEAD_BYTES = 4096;
@@ -3241,7 +4585,7 @@ function trackTempDir(dir) {
3241
4585
  let done = false;
3242
4586
  const remove = () => {
3243
4587
  try {
3244
- fs18.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
4588
+ fs20.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
3245
4589
  } catch {
3246
4590
  }
3247
4591
  };
@@ -3269,9 +4613,9 @@ async function stdinToTempFile(opts = {}) {
3269
4613
  }
3270
4614
  const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
3271
4615
  const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
3272
- const dir = fs18.mkdtempSync(path21.join(os2.tmpdir(), "multix-stdin-"));
4616
+ const dir = fs20.mkdtempSync(path23.join(os2.tmpdir(), "multix-stdin-"));
3273
4617
  const cleanup = trackTempDir(dir);
3274
- const rawPath = path21.join(dir, "input");
4618
+ const rawPath = path23.join(dir, "input");
3275
4619
  const hint = setTimeout(() => {
3276
4620
  process.stderr.write("Waiting for data on stdin (`--files -`)... press Ctrl+C to cancel\n");
3277
4621
  }, WAIT_HINT_MS);
@@ -3279,7 +4623,7 @@ async function stdinToTempFile(opts = {}) {
3279
4623
  let head = Buffer.alloc(0);
3280
4624
  let size = 0;
3281
4625
  try {
3282
- const fd = fs18.openSync(rawPath, "w", 384);
4626
+ const fd = fs20.openSync(rawPath, "w", 384);
3283
4627
  try {
3284
4628
  for await (const chunk of input) {
3285
4629
  clearTimeout(hint);
@@ -3293,17 +4637,17 @@ async function stdinToTempFile(opts = {}) {
3293
4637
  head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
3294
4638
  }
3295
4639
  let offset = 0;
3296
- while (offset < buf.length) offset += fs18.writeSync(fd, buf, offset);
4640
+ while (offset < buf.length) offset += fs20.writeSync(fd, buf, offset);
3297
4641
  size += buf.length;
3298
4642
  }
3299
4643
  } finally {
3300
4644
  clearTimeout(hint);
3301
- fs18.closeSync(fd);
4645
+ fs20.closeSync(fd);
3302
4646
  }
3303
4647
  if (size === 0) throw new ValidationError("`--files -` was given but stdin was empty");
3304
4648
  const detectedExt = forcedExt ?? detectExtension(head);
3305
- const finalPath = path21.join(dir, `stdin${detectedExt ?? ".bin"}`);
3306
- fs18.renameSync(rawPath, finalPath);
4649
+ const finalPath = path23.join(dir, `stdin${detectedExt ?? ".bin"}`);
4650
+ fs20.renameSync(rawPath, finalPath);
3307
4651
  return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
3308
4652
  } catch (error) {
3309
4653
  cleanup();
@@ -3320,7 +4664,7 @@ async function resolveFileInputs(files, opts = {}) {
3320
4664
  throw new ValidationError("--stdin-ext only applies when --files includes `-`");
3321
4665
  }
3322
4666
  for (const f of files) {
3323
- if (f !== STDIN_ARG && !fs18.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
4667
+ if (f !== STDIN_ARG && !fs20.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
3324
4668
  }
3325
4669
  if (stdinCount === 0) {
3326
4670
  return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
@@ -3347,8 +4691,8 @@ async function resolveFileInputs(files, opts = {}) {
3347
4691
 
3348
4692
  // src/providers/gemini/client.ts
3349
4693
  init_errors();
3350
- import fs19 from "fs";
3351
- import path23 from "path";
4694
+ import fs21 from "fs";
4695
+ import path25 from "path";
3352
4696
 
3353
4697
  // src/providers/gemini/key-pool.ts
3354
4698
  init_errors();
@@ -3414,7 +4758,7 @@ async function withGeminiKey(fn, opts = {}) {
3414
4758
  }
3415
4759
 
3416
4760
  // src/providers/gemini/task-resolver.ts
3417
- import path22 from "path";
4761
+ import path24 from "path";
3418
4762
  var MIME_MAP = {
3419
4763
  // Audio
3420
4764
  ".mp3": "audio/mp3",
@@ -3455,7 +4799,7 @@ var MIME_MAP = {
3455
4799
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
3456
4800
  };
3457
4801
  function getMimeType(filePath) {
3458
- const ext = path22.extname(filePath).toLowerCase();
4802
+ const ext = path24.extname(filePath).toLowerCase();
3459
4803
  return MIME_MAP[ext] ?? "application/octet-stream";
3460
4804
  }
3461
4805
  function requiresProcessingWait(mimeType) {
@@ -3478,8 +4822,8 @@ async function uploadFile(filePath, opts = {}) {
3478
4822
  async function uploadFileWithKey(filePath, apiKey, opts) {
3479
4823
  const { timeoutMs = 3e5, logger } = opts;
3480
4824
  const mimeType = getMimeType(filePath);
3481
- const fileBytes = fs19.readFileSync(filePath);
3482
- const displayName = path23.basename(filePath);
4825
+ const fileBytes = fs21.readFileSync(filePath);
4826
+ const displayName = path25.basename(filePath);
3483
4827
  const boundary = `multix_${Date.now()}`;
3484
4828
  const metaJson = JSON.stringify({ file: { displayName, mimeType } });
3485
4829
  const body = Buffer.concat([
@@ -3596,12 +4940,12 @@ function extractImages(resp) {
3596
4940
  ).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
3597
4941
  }
3598
4942
  function inlinePart(filePath) {
3599
- const data = fs19.readFileSync(filePath).toString("base64");
4943
+ const data = fs21.readFileSync(filePath).toString("base64");
3600
4944
  const mimeType = getMimeType(filePath);
3601
4945
  return { inlineData: { mimeType, data } };
3602
4946
  }
3603
4947
  function shouldUseFileApi(filePath) {
3604
- const stat = fs19.statSync(filePath);
4948
+ const stat = fs21.statSync(filePath);
3605
4949
  return stat.size > 20 * 1024 * 1024;
3606
4950
  }
3607
4951
  function sleep2(ms) {
@@ -3655,13 +4999,13 @@ function getDefaultModel(task) {
3655
4999
  }
3656
5000
 
3657
5001
  // src/providers/gemini/result-formatter.ts
3658
- import fs20 from "fs";
3659
- import path24 from "path";
5002
+ import fs22 from "fs";
5003
+ import path26 from "path";
3660
5004
  function formatResults(results, format, outputPath) {
3661
5005
  const content = renderFormat(results, format);
3662
5006
  if (outputPath) {
3663
- fs20.mkdirSync(path24.dirname(path24.resolve(outputPath)), { recursive: true });
3664
- fs20.writeFileSync(outputPath, content, "utf8");
5007
+ fs22.mkdirSync(path26.dirname(path26.resolve(outputPath)), { recursive: true });
5008
+ fs22.writeFileSync(outputPath, content, "utf8");
3665
5009
  } else {
3666
5010
  process.stdout.write(content);
3667
5011
  }
@@ -3845,8 +5189,8 @@ function registerExtractCommand(parent) {
3845
5189
  init_errors();
3846
5190
 
3847
5191
  // src/providers/gemini/generators/speech.ts
3848
- import fs21 from "fs";
3849
- import path25 from "path";
5192
+ import fs23 from "fs";
5193
+ import path27 from "path";
3850
5194
 
3851
5195
  // src/providers/gemini/voices.ts
3852
5196
  var GEMINI_TTS_VOICES = [
@@ -4012,12 +5356,12 @@ function extractWavData(wav) {
4012
5356
  }
4013
5357
  function saveSpeech(outBytes, outputFormat, output, logger) {
4014
5358
  const outDir = getOutputDir();
4015
- const dest = path25.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
4016
- fs21.writeFileSync(dest, outBytes);
5359
+ const dest = path27.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
5360
+ fs23.writeFileSync(dest, outBytes);
4017
5361
  logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
4018
5362
  if (output) {
4019
- fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
4020
- fs21.copyFileSync(dest, output);
5363
+ fs23.mkdirSync(path27.dirname(path27.resolve(output)), { recursive: true });
5364
+ fs23.copyFileSync(dest, output);
4021
5365
  logger?.success(`Copied to: ${output}`);
4022
5366
  }
4023
5367
  return dest;
@@ -4130,84 +5474,24 @@ function collectSpeakers(value, prev) {
4130
5474
 
4131
5475
  // src/providers/gemini/commands/generate-video.ts
4132
5476
  init_errors();
4133
- import fs23 from "fs";
4134
- import path26 from "path";
4135
-
4136
- // src/providers/gemini/veo.ts
4137
- import fs22 from "fs";
4138
-
4139
- // src/core/async-poll.ts
4140
- init_errors();
4141
- var PollTimeoutError2 = class extends MultixError {
4142
- constructor(message, lastStatus) {
4143
- super(message, "POLL_TIMEOUT");
4144
- this.lastStatus = lastStatus;
4145
- this.name = "PollTimeoutError";
4146
- }
4147
- lastStatus;
4148
- };
4149
- function isTransientHttpError(error) {
4150
- if (!(error instanceof HttpError)) return false;
4151
- return error.status === 0 || error.status === 429 || error.status >= 500;
4152
- }
4153
- var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4154
- async function pollUntil2(opts) {
4155
- const now = opts.now ?? Date.now;
4156
- const sleep5 = opts.sleep ?? defaultSleep;
4157
- const random = opts.random ?? Math.random;
4158
- const isTransient = opts.isTransientError ?? isTransientHttpError;
4159
- const maxInterval = opts.maxIntervalMs ?? 15e3;
4160
- const minInterval = opts.minIntervalMs ?? 500;
4161
- const factor = opts.backoffFactor ?? 1.5;
4162
- const jitter = opts.jitter ?? true;
4163
- const deadline = now() + opts.timeoutMs;
4164
- let interval = opts.intervalMs ?? 3e3;
4165
- let attempt = 0;
4166
- let last;
4167
- let lastError;
4168
- for (; ; ) {
4169
- attempt++;
4170
- let retryAfterMs;
4171
- try {
4172
- last = await opts.fetchStatus({ remainingMs: Math.max(1, deadline - now()), attempt });
4173
- lastError = void 0;
4174
- opts.onPoll?.(last, attempt);
4175
- if (opts.isTerminal(last)) return last;
4176
- } catch (error) {
4177
- if (!isTransient(error)) throw error;
4178
- lastError = error;
4179
- if (error instanceof HttpError) retryAfterMs = error.retryAfterMs;
4180
- }
4181
- const remaining = deadline - now();
4182
- if (remaining <= 0) {
4183
- const hint = opts.resumeHint ? ` ${opts.resumeHint}` : "";
4184
- const cause = lastError instanceof Error ? ` Last error: ${lastError.message}.` : "";
4185
- throw new PollTimeoutError2(
4186
- `Timed out after ${opts.timeoutMs}ms waiting for job.${cause}${hint}`,
4187
- last
4188
- );
4189
- }
4190
- let delay = retryAfterMs ?? interval;
4191
- if (jitter && retryAfterMs === void 0) delay *= 0.8 + random() * 0.4;
4192
- await sleep5(Math.min(Math.max(delay, minInterval), remaining));
4193
- interval = Math.min(interval * factor, maxInterval);
4194
- }
4195
- }
5477
+ import fs25 from "fs";
5478
+ import path28 from "path";
4196
5479
 
4197
5480
  // src/providers/gemini/veo.ts
5481
+ import fs24 from "fs";
4198
5482
  init_errors();
4199
5483
  var API_HOST = "generativelanguage.googleapis.com";
4200
5484
  var BASE2 = `https://${API_HOST}/v1beta`;
4201
5485
  var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
4202
5486
  var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
4203
5487
  function veoImageFromFile(imgPath) {
4204
- if (!fs22.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
5488
+ if (!fs24.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
4205
5489
  const detected = getMimeType(imgPath);
4206
5490
  const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
4207
5491
  if (!VEO_IMAGE_TYPES.has(mimeType)) {
4208
5492
  throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
4209
5493
  }
4210
- return { bytesBase64Encoded: fs22.readFileSync(imgPath).toString("base64"), mimeType };
5494
+ return { bytesBase64Encoded: fs24.readFileSync(imgPath).toString("base64"), mimeType };
4211
5495
  }
4212
5496
  function veoFramesFromPaths(paths = []) {
4213
5497
  if (paths.length > 2) {
@@ -4256,7 +5540,7 @@ async function submitVeo(apiKey, model, body) {
4256
5540
  }
4257
5541
  async function waitForVeo(apiKey, operation, logger, opts = {}) {
4258
5542
  if (operation.done) return checkVeoResult(operation);
4259
- const finalOp = await pollUntil2({
5543
+ const finalOp = await pollUntil({
4260
5544
  fetchStatus: ({ remainingMs }) => httpJson({
4261
5545
  url: `${BASE2}/${operation.name}`,
4262
5546
  headers: headers(apiKey),
@@ -4332,7 +5616,7 @@ function registerGenerateVideoCommand(parent) {
4332
5616
  logger.debug(`Submitting video generation with ${model}...`);
4333
5617
  logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
4334
5618
  const frames = veoFramesFromPaths(opts.referenceImages);
4335
- const dest = path26.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
5619
+ const dest = path28.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
4336
5620
  const finalOp = await runVeo(
4337
5621
  model,
4338
5622
  {
@@ -4346,8 +5630,8 @@ function registerGenerateVideoCommand(parent) {
4346
5630
  );
4347
5631
  logger.success(`Saved video: ${dest}`);
4348
5632
  if (opts.output) {
4349
- fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
4350
- fs23.copyFileSync(dest, opts.output);
5633
+ fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
5634
+ fs25.copyFileSync(dest, opts.output);
4351
5635
  logger.success(`Copied to: ${opts.output}`);
4352
5636
  }
4353
5637
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4363,8 +5647,8 @@ Generated video: ${dest}`);
4363
5647
 
4364
5648
  // src/providers/gemini/commands/generate.ts
4365
5649
  init_errors();
4366
- import fs24 from "fs";
4367
- import path27 from "path";
5650
+ import fs26 from "fs";
5651
+ import path29 from "path";
4368
5652
  function registerGenerateCommand(parent) {
4369
5653
  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(
4370
5654
  async (opts) => {
@@ -4409,8 +5693,8 @@ function registerGenerateCommand(parent) {
4409
5693
  const outDir = getOutputDir();
4410
5694
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
4411
5695
  const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
4412
- const dest = path27.join(outDir, fname);
4413
- fs24.writeFileSync(dest, Buffer.from(img.data, "base64"));
5696
+ const dest = path29.join(outDir, fname);
5697
+ fs26.writeFileSync(dest, Buffer.from(img.data, "base64"));
4414
5698
  savedFiles.push(dest);
4415
5699
  logger.success(`Saved: ${dest}`);
4416
5700
  }
@@ -4425,8 +5709,8 @@ function registerGenerateCommand(parent) {
4425
5709
  process.exit(1);
4426
5710
  }
4427
5711
  if (opts.output && savedFiles[0]) {
4428
- fs24.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
4429
- fs24.copyFileSync(savedFiles[0], opts.output);
5712
+ fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
5713
+ fs26.copyFileSync(savedFiles[0], opts.output);
4430
5714
  logger.success(`Copied to: ${opts.output}`);
4431
5715
  }
4432
5716
  const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
@@ -4438,8 +5722,8 @@ Generated ${finalImages.length} image(s):`);
4438
5722
  }
4439
5723
 
4440
5724
  // src/providers/gemini/commands/image-to-image.ts
4441
- import fs25 from "fs";
4442
- import path28 from "path";
5725
+ import fs27 from "fs";
5726
+ import path30 from "path";
4443
5727
  var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
4444
5728
  var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
4445
5729
  function registerGeminiImageToImageCommand(parent) {
@@ -4500,14 +5784,14 @@ function registerGeminiImageToImageCommand(parent) {
4500
5784
  const img = images[i];
4501
5785
  if (!img) continue;
4502
5786
  const ext = img.mimeType.includes("png") ? "png" : "jpg";
4503
- const dest = path28.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
4504
- fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
5787
+ const dest = path30.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
5788
+ fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
4505
5789
  saved.push(dest);
4506
5790
  logger.success(`Saved: ${dest}`);
4507
5791
  }
4508
5792
  if (opts.output && saved[0]) {
4509
- fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
4510
- fs25.copyFileSync(saved[0], opts.output);
5793
+ fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
5794
+ fs27.copyFileSync(saved[0], opts.output);
4511
5795
  logger.success(`Copied to: ${opts.output}`);
4512
5796
  }
4513
5797
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4535,7 +5819,7 @@ function sniffImageMime(buf, url) {
4535
5819
  if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
4536
5820
  return "image/webp";
4537
5821
  }
4538
- const ext = path28.extname(new URL(url).pathname).toLowerCase();
5822
+ const ext = path30.extname(new URL(url).pathname).toLowerCase();
4539
5823
  if (ext === ".png") return "image/png";
4540
5824
  if (ext === ".webp") return "image/webp";
4541
5825
  if (ext === ".gif") return "image/gif";
@@ -4544,8 +5828,8 @@ function sniffImageMime(buf, url) {
4544
5828
 
4545
5829
  // src/providers/gemini/commands/image-to-video.ts
4546
5830
  init_errors();
4547
- import fs26 from "fs";
4548
- import path29 from "path";
5831
+ import fs28 from "fs";
5832
+ import path31 from "path";
4549
5833
  function registerGeminiImageToVideoCommand(parent) {
4550
5834
  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(
4551
5835
  async (imagePath, opts) => {
@@ -4565,7 +5849,7 @@ function registerGeminiImageToVideoCommand(parent) {
4565
5849
  logger.info(
4566
5850
  "Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
4567
5851
  );
4568
- const dest = path29.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
5852
+ const dest = path31.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
4569
5853
  const finalOp = await runVeo(
4570
5854
  model,
4571
5855
  {
@@ -4580,8 +5864,8 @@ function registerGeminiImageToVideoCommand(parent) {
4580
5864
  );
4581
5865
  logger.success(`Saved video: ${dest}`);
4582
5866
  if (opts.output) {
4583
- fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
4584
- fs26.copyFileSync(dest, opts.output);
5867
+ fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
5868
+ fs28.copyFileSync(dest, opts.output);
4585
5869
  logger.success(`Copied to: ${opts.output}`);
4586
5870
  }
4587
5871
  await maybeDownloadThumb(finalOp.response, dest, {
@@ -4674,8 +5958,8 @@ function registerGeminiCommands(program2) {
4674
5958
  }
4675
5959
 
4676
5960
  // src/providers/leonardo/commands/generate.ts
4677
- import fs27 from "fs";
4678
- import path30 from "path";
5961
+ import fs29 from "fs";
5962
+ import path32 from "path";
4679
5963
 
4680
5964
  // src/providers/leonardo/client.ts
4681
5965
  init_errors();
@@ -4748,11 +6032,11 @@ var LeonardoClient = class {
4748
6032
  }
4749
6033
  apiKey;
4750
6034
  baseUrl;
4751
- async request(path50, opts = {}) {
6035
+ async request(path52, opts = {}) {
4752
6036
  const base = this.baseUrl.replace(/\/$/, "");
4753
- const v2Override = path50.startsWith("v2:");
6037
+ const v2Override = path52.startsWith("v2:");
4754
6038
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
4755
- const finalPath = v2Override ? path50.slice(3) : path50;
6039
+ const finalPath = v2Override ? path52.slice(3) : path52;
4756
6040
  const url = new URL(finalBase + finalPath);
4757
6041
  if (opts.query) {
4758
6042
  for (const [k, v] of Object.entries(opts.query)) {
@@ -4771,14 +6055,14 @@ var LeonardoClient = class {
4771
6055
  timeoutMs: opts.timeoutMs ?? 12e4
4772
6056
  });
4773
6057
  }
4774
- get(path50, query, logger) {
4775
- return this.request(path50, { method: "GET", query, logger });
6058
+ get(path52, query, logger) {
6059
+ return this.request(path52, { method: "GET", query, logger });
4776
6060
  }
4777
- post(path50, body, logger) {
4778
- return this.request(path50, { method: "POST", body, logger });
6061
+ post(path52, body, logger) {
6062
+ return this.request(path52, { method: "POST", body, logger });
4779
6063
  }
4780
- delete(path50, logger) {
4781
- return this.request(path50, { method: "DELETE", logger });
6064
+ delete(path52, logger) {
6065
+ return this.request(path52, { method: "DELETE", logger });
4782
6066
  }
4783
6067
  };
4784
6068
  function createLeonardoClient() {
@@ -4789,7 +6073,7 @@ function createLeonardoClient() {
4789
6073
  function extFromUrl2(url, fallback = ".png") {
4790
6074
  try {
4791
6075
  const u = new URL(url);
4792
- const e = path30.extname(u.pathname);
6076
+ const e = path32.extname(u.pathname);
4793
6077
  return e.length > 0 ? e : fallback;
4794
6078
  } catch {
4795
6079
  return fallback;
@@ -4874,7 +6158,7 @@ function registerLeonardoGenerateCommand(parent) {
4874
6158
  if (!img) continue;
4875
6159
  const ext = extFromUrl2(img.url);
4876
6160
  const filename = autoFilename(generationId, i + 1, ext);
4877
- const filepath = path30.join(outDir, filename);
6161
+ const filepath = path32.join(outDir, filename);
4878
6162
  try {
4879
6163
  await downloadFile(img.url, filepath);
4880
6164
  saved.push(filepath);
@@ -4884,9 +6168,9 @@ function registerLeonardoGenerateCommand(parent) {
4884
6168
  }
4885
6169
  }
4886
6170
  if (opts.output && saved[0]) {
4887
- const dest = path30.resolve(opts.output);
4888
- fs27.mkdirSync(path30.dirname(dest), { recursive: true });
4889
- fs27.copyFileSync(saved[0], dest);
6171
+ const dest = path32.resolve(opts.output);
6172
+ fs29.mkdirSync(path32.dirname(dest), { recursive: true });
6173
+ fs29.copyFileSync(saved[0], dest);
4890
6174
  logger.success(`Copied to ${dest}`);
4891
6175
  }
4892
6176
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4898,11 +6182,11 @@ Generated ${finalImages.length} image(s):`);
4898
6182
  }
4899
6183
 
4900
6184
  // src/providers/leonardo/commands/image-to-image.ts
4901
- import fs28 from "fs";
4902
- import path31 from "path";
6185
+ import fs30 from "fs";
6186
+ import path33 from "path";
4903
6187
  function extFromUrl3(url, fallback = ".png") {
4904
6188
  try {
4905
- return path31.extname(new URL(url).pathname) || fallback;
6189
+ return path33.extname(new URL(url).pathname) || fallback;
4906
6190
  } catch {
4907
6191
  return fallback;
4908
6192
  }
@@ -4964,7 +6248,7 @@ function registerLeonardoImageToImageCommand(parent) {
4964
6248
  if (!img) continue;
4965
6249
  const ext = extFromUrl3(img.url);
4966
6250
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
4967
- const filepath = path31.join(outDir, filename);
6251
+ const filepath = path33.join(outDir, filename);
4968
6252
  try {
4969
6253
  await downloadFile(img.url, filepath);
4970
6254
  saved.push(filepath);
@@ -4974,9 +6258,9 @@ function registerLeonardoImageToImageCommand(parent) {
4974
6258
  }
4975
6259
  }
4976
6260
  if (opts.output && saved[0]) {
4977
- const dest = path31.resolve(opts.output);
4978
- fs28.mkdirSync(path31.dirname(dest), { recursive: true });
4979
- fs28.copyFileSync(saved[0], dest);
6261
+ const dest = path33.resolve(opts.output);
6262
+ fs30.mkdirSync(path33.dirname(dest), { recursive: true });
6263
+ fs30.copyFileSync(saved[0], dest);
4980
6264
  logger.success(`Copied to ${dest}`);
4981
6265
  }
4982
6266
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -4988,7 +6272,7 @@ Generated ${finalImages.length} image(s):`);
4988
6272
  }
4989
6273
 
4990
6274
  // src/providers/leonardo/commands/video.ts
4991
- import path32 from "path";
6275
+ import path34 from "path";
4992
6276
  function v2DimsFor(resolution) {
4993
6277
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
4994
6278
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -5073,7 +6357,7 @@ async function pollAndDownload(client, jobId, opts) {
5073
6357
  onTick: (n, v) => logger.debug(`attempt ${n} \u2014 status: ${v.generations_by_pk?.status}`)
5074
6358
  });
5075
6359
  } catch (e) {
5076
- if (e instanceof PollTimeoutError) {
6360
+ if (e instanceof PollTimeoutError2) {
5077
6361
  logger.error(`Timed out \u2014 multix leonardo status ${jobId}`);
5078
6362
  process.exit(1);
5079
6363
  }
@@ -5099,7 +6383,7 @@ async function pollAndDownload(client, jobId, opts) {
5099
6383
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
5100
6384
  process.exit(1);
5101
6385
  }
5102
- const outPath = opts.output ?? path32.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
6386
+ const outPath = opts.output ?? path34.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
5103
6387
  await downloadFile(videoUrl2, outPath);
5104
6388
  logger.success(`Saved ${outPath}`);
5105
6389
  await maybeDownloadThumb(gen, outPath, {
@@ -5205,7 +6489,7 @@ function registerLeonardoModelsCommand(parent) {
5205
6489
  }
5206
6490
 
5207
6491
  // src/providers/leonardo/commands/status.ts
5208
- import path33 from "path";
6492
+ import path35 from "path";
5209
6493
  var VIDEO_KEYS = ["motionMP4URL", "url"];
5210
6494
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
5211
6495
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -5245,7 +6529,7 @@ function registerLeonardoStatusCommand(parent) {
5245
6529
  onTick: (n, v) => logger.debug(`attempt ${n} \u2014 status: ${v.generations_by_pk?.status}`)
5246
6530
  }) : await fetchOnce();
5247
6531
  } catch (e) {
5248
- if (e instanceof PollTimeoutError) {
6532
+ if (e instanceof PollTimeoutError2) {
5249
6533
  logger.error(`Timed out waiting for generation ${generationId}`);
5250
6534
  process.exit(1);
5251
6535
  }
@@ -5270,7 +6554,7 @@ function registerLeonardoStatusCommand(parent) {
5270
6554
  const outDir = getOutputDir();
5271
6555
  let idx = 0;
5272
6556
  for (const url of videos) {
5273
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
6557
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path35.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
5274
6558
  await downloadFile(url, dest);
5275
6559
  logger.success(`Saved ${dest}`);
5276
6560
  await maybeDownloadThumb(gen, dest, {
@@ -5283,7 +6567,7 @@ function registerLeonardoStatusCommand(parent) {
5283
6567
  for (const url of images) {
5284
6568
  const m = url.match(IMAGE_EXT_RE2);
5285
6569
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
5286
- const dest = path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
6570
+ const dest = path35.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
5287
6571
  await downloadFile(url, dest);
5288
6572
  logger.success(`Saved ${dest}`);
5289
6573
  idx++;
@@ -5440,8 +6724,8 @@ function sleep3(ms) {
5440
6724
  }
5441
6725
 
5442
6726
  // src/providers/minimax/generators/music.ts
5443
- import fs29 from "fs";
5444
- import path34 from "path";
6727
+ import fs31 from "fs";
6728
+ import path36 from "path";
5445
6729
  async function generateMinimaxMusic(opts) {
5446
6730
  const {
5447
6731
  apiKey,
@@ -5486,14 +6770,14 @@ async function generateMinimaxMusic(opts) {
5486
6770
  }
5487
6771
  const durationMs = resp.extra_info?.music_duration ?? 0;
5488
6772
  const outDir = getOutputDir();
5489
- const dest = path34.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
6773
+ const dest = path36.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
5490
6774
  try {
5491
6775
  if (audioData.startsWith("http")) {
5492
6776
  const bytes = await fetchBytes(audioData);
5493
- fs29.writeFileSync(dest, bytes);
6777
+ fs31.writeFileSync(dest, bytes);
5494
6778
  } else {
5495
6779
  const audioBytes = Buffer.from(audioData, "hex");
5496
- fs29.writeFileSync(dest, audioBytes);
6780
+ fs31.writeFileSync(dest, audioBytes);
5497
6781
  }
5498
6782
  } catch (e) {
5499
6783
  return {
@@ -5504,8 +6788,8 @@ async function generateMinimaxMusic(opts) {
5504
6788
  const durSec = durationMs / 1e3;
5505
6789
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
5506
6790
  if (output) {
5507
- fs29.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
5508
- fs29.copyFileSync(dest, output);
6791
+ fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
6792
+ fs31.copyFileSync(dest, output);
5509
6793
  }
5510
6794
  return { status: "success", generatedAudio: dest, durationMs, model };
5511
6795
  }
@@ -5579,8 +6863,8 @@ Generated music: ${result.generatedAudio}`);
5579
6863
  init_errors();
5580
6864
 
5581
6865
  // src/providers/minimax/generators/speech.ts
5582
- import fs30 from "fs";
5583
- import path35 from "path";
6866
+ import fs32 from "fs";
6867
+ import path37 from "path";
5584
6868
  async function generateMinimaxSpeech(opts) {
5585
6869
  const {
5586
6870
  apiKey,
@@ -5627,12 +6911,12 @@ async function generateMinimaxSpeech(opts) {
5627
6911
  const audioBytes = Buffer.from(audioHex, "hex");
5628
6912
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
5629
6913
  const outDir = getOutputDir();
5630
- const dest = path35.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
5631
- fs30.writeFileSync(dest, audioBytes);
6914
+ const dest = path37.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
6915
+ fs32.writeFileSync(dest, audioBytes);
5632
6916
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
5633
6917
  if (output) {
5634
- fs30.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
5635
- fs30.copyFileSync(dest, output);
6918
+ fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
6919
+ fs32.copyFileSync(dest, output);
5636
6920
  }
5637
6921
  return { status: "success", generatedAudio: dest, model };
5638
6922
  }
@@ -5679,8 +6963,8 @@ Generated audio: ${result.generatedAudio}`);
5679
6963
  init_errors();
5680
6964
 
5681
6965
  // src/providers/minimax/generators/video.ts
5682
- import fs31 from "fs";
5683
- import path36 from "path";
6966
+ import fs33 from "fs";
6967
+ import path38 from "path";
5684
6968
  async function generateMinimaxVideo(opts) {
5685
6969
  const {
5686
6970
  apiKey,
@@ -5726,18 +7010,18 @@ async function generateMinimaxVideo(opts) {
5726
7010
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
5727
7011
  }
5728
7012
  const outDir = getOutputDir();
5729
- const dest = path36.join(outDir, `minimax_video_${Date.now()}.mp4`);
7013
+ const dest = path38.join(outDir, `minimax_video_${Date.now()}.mp4`);
5730
7014
  try {
5731
7015
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
5732
7016
  } catch (e) {
5733
7017
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
5734
7018
  }
5735
7019
  const elapsed = (Date.now() - start) / 1e3;
5736
- const stat = fs31.statSync(dest);
7020
+ const stat = fs33.statSync(dest);
5737
7021
  const fileSizeMb = stat.size / (1024 * 1024);
5738
7022
  if (output) {
5739
- fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
5740
- fs31.copyFileSync(dest, output);
7023
+ fs33.mkdirSync(path38.dirname(path38.resolve(output)), { recursive: true });
7024
+ fs33.copyFileSync(dest, output);
5741
7025
  }
5742
7026
  await maybeDownloadThumb(pollResult, dest, {
5743
7027
  skip: thumb === false,
@@ -5799,8 +7083,8 @@ Generated video: ${result.generatedVideo}`);
5799
7083
 
5800
7084
  // src/providers/minimax/generators/image.ts
5801
7085
  init_errors();
5802
- import fs32 from "fs";
5803
- import path37 from "path";
7086
+ import fs34 from "fs";
7087
+ import path39 from "path";
5804
7088
  async function generateMinimaxImage(opts) {
5805
7089
  const {
5806
7090
  apiKey,
@@ -5838,8 +7122,8 @@ async function generateMinimaxImage(opts) {
5838
7122
  try {
5839
7123
  const bytes = await fetchBytes(url);
5840
7124
  const fname = `minimax_image_${Date.now()}_${i}.png`;
5841
- const dest = path37.join(outDir, fname);
5842
- fs32.writeFileSync(dest, bytes);
7125
+ const dest = path39.join(outDir, fname);
7126
+ fs34.writeFileSync(dest, bytes);
5843
7127
  savedFiles.push(dest);
5844
7128
  logger?.success(`Saved: ${dest}`);
5845
7129
  } catch (e) {
@@ -5850,8 +7134,8 @@ async function generateMinimaxImage(opts) {
5850
7134
  }
5851
7135
  }
5852
7136
  if (output && savedFiles[0]) {
5853
- fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
5854
- fs32.copyFileSync(savedFiles[0], output);
7137
+ fs34.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
7138
+ fs34.copyFileSync(savedFiles[0], output);
5855
7139
  }
5856
7140
  return { status: "success", generatedImages: savedFiles, model };
5857
7141
  }
@@ -5890,8 +7174,8 @@ Generated ${finalImages.length} image(s):`);
5890
7174
 
5891
7175
  // src/providers/minimax/commands/image-to-image.ts
5892
7176
  init_errors();
5893
- import fs33 from "fs";
5894
- import path38 from "path";
7177
+ import fs35 from "fs";
7178
+ import path40 from "path";
5895
7179
  function registerMinimaxImageToImageCommand(parent) {
5896
7180
  parent.command("image-to-image").alias("i2i").description(
5897
7181
  "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."
@@ -5946,8 +7230,8 @@ function registerMinimaxImageToImageCommand(parent) {
5946
7230
  if (!url) continue;
5947
7231
  try {
5948
7232
  const bytes = await fetchBytes(url);
5949
- const dest = path38.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
5950
- fs33.writeFileSync(dest, bytes);
7233
+ const dest = path40.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
7234
+ fs35.writeFileSync(dest, bytes);
5951
7235
  saved.push(dest);
5952
7236
  logger.success(`Saved: ${dest}`);
5953
7237
  } catch (e) {
@@ -5958,8 +7242,8 @@ function registerMinimaxImageToImageCommand(parent) {
5958
7242
  }
5959
7243
  }
5960
7244
  if (opts.output && saved[0]) {
5961
- fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
5962
- fs33.copyFileSync(saved[0], opts.output);
7245
+ fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
7246
+ fs35.copyFileSync(saved[0], opts.output);
5963
7247
  logger.success(`Copied to: ${opts.output}`);
5964
7248
  }
5965
7249
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -6093,12 +7377,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
6093
7377
  }
6094
7378
 
6095
7379
  // src/providers/openai/openai-audio.ts
6096
- import fs35 from "fs";
6097
- import path40 from "path";
7380
+ import fs37 from "fs";
7381
+ import path42 from "path";
6098
7382
 
6099
7383
  // src/providers/openai/client.ts
6100
- import fs34 from "fs";
6101
- import path39 from "path";
7384
+ import fs36 from "fs";
7385
+ import path41 from "path";
6102
7386
  init_errors();
6103
7387
  var DEFAULT_TIMEOUT_MS3 = 24e4;
6104
7388
  function requireOpenAIKey() {
@@ -6164,10 +7448,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
6164
7448
  return Buffer.from(await res.arrayBuffer());
6165
7449
  }
6166
7450
  function fileFromPath(filePath) {
6167
- const abs = path39.resolve(filePath);
6168
- if (!fs34.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
6169
- const bytes = fs34.readFileSync(abs);
6170
- return new File([new Uint8Array(bytes)], path39.basename(abs));
7451
+ const abs = path41.resolve(filePath);
7452
+ if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
7453
+ const bytes = fs36.readFileSync(abs);
7454
+ return new File([new Uint8Array(bytes)], path41.basename(abs));
6171
7455
  }
6172
7456
 
6173
7457
  // src/providers/openai/openai-audio.ts
@@ -6206,7 +7490,7 @@ async function resolveAudioReferenceDataUrl(ref) {
6206
7490
  }
6207
7491
  function audioMimeFromReference(ref) {
6208
7492
  const pathname = new URL(ref).pathname;
6209
- const ext = path40.extname(pathname).toLowerCase();
7493
+ const ext = path42.extname(pathname).toLowerCase();
6210
7494
  switch (ext) {
6211
7495
  case ".mp3":
6212
7496
  return "audio/mpeg";
@@ -6225,12 +7509,12 @@ function audioMimeFromReference(ref) {
6225
7509
  async function generateOpenAISpeech(opts) {
6226
7510
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
6227
7511
  const ext = extFromAudioFormat(opts.outputFormat);
6228
- const dest = path40.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
6229
- fs35.writeFileSync(dest, bytes);
7512
+ const dest = path42.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
7513
+ fs37.writeFileSync(dest, bytes);
6230
7514
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
6231
7515
  if (opts.output) {
6232
- fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
6233
- fs35.copyFileSync(dest, opts.output);
7516
+ fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
7517
+ fs37.copyFileSync(dest, opts.output);
6234
7518
  }
6235
7519
  return dest;
6236
7520
  }
@@ -6252,9 +7536,9 @@ async function transcribeOpenAIAudio(opts) {
6252
7536
  );
6253
7537
  const text = typeof data.text === "string" ? data.text : void 0;
6254
7538
  const ext = opts.format === "text" ? "txt" : "json";
6255
- const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
6256
- fs35.mkdirSync(path40.dirname(dest), { recursive: true });
6257
- fs35.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
7539
+ const dest = opts.output ? path42.resolve(opts.output) : path42.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
7540
+ fs37.mkdirSync(path42.dirname(dest), { recursive: true });
7541
+ fs37.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
6258
7542
  opts.logger?.success(`Saved transcript: ${dest}`);
6259
7543
  return { path: dest, text };
6260
7544
  }
@@ -6281,9 +7565,9 @@ Generated audio: ${dest}`);
6281
7565
  }
6282
7566
 
6283
7567
  // src/providers/openai/codex-image-driver.ts
6284
- import fs36 from "fs";
7568
+ import fs38 from "fs";
6285
7569
  import os3 from "os";
6286
- import path41 from "path";
7570
+ import path43 from "path";
6287
7571
  import { execa } from "execa";
6288
7572
  init_errors();
6289
7573
  var defaultRunner = async (cmd, args, opts) => {
@@ -6332,12 +7616,12 @@ function sanitizeCodexEnv() {
6332
7616
  async function runCodexImageGeneration(opts) {
6333
7617
  const runner = opts.runner ?? defaultRunner;
6334
7618
  const ext = extFromImageFormat(opts.outputFormat);
6335
- const dest = opts.output ? path41.resolve(opts.output) : path41.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
6336
- fs36.mkdirSync(path41.dirname(dest), { recursive: true });
6337
- const tmp = fs36.mkdtempSync(path41.join(os3.tmpdir(), "multix-codex-image-"));
6338
- const schema = path41.join(tmp, "schema.json");
6339
- const result = path41.join(tmp, "result.json");
6340
- fs36.writeFileSync(
7619
+ const dest = opts.output ? path43.resolve(opts.output) : path43.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
7620
+ fs38.mkdirSync(path43.dirname(dest), { recursive: true });
7621
+ const tmp = fs38.mkdtempSync(path43.join(os3.tmpdir(), "multix-codex-image-"));
7622
+ const schema = path43.join(tmp, "schema.json");
7623
+ const result = path43.join(tmp, "result.json");
7624
+ fs38.writeFileSync(
6341
7625
  schema,
6342
7626
  JSON.stringify({
6343
7627
  type: "object",
@@ -6367,7 +7651,7 @@ async function runCodexImageGeneration(opts) {
6367
7651
  if (res.exitCode !== 0) {
6368
7652
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
6369
7653
  }
6370
- if (!fs36.existsSync(dest) || fs36.statSync(dest).size === 0) {
7654
+ if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
6371
7655
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
6372
7656
  }
6373
7657
  return { path: dest };
@@ -6384,8 +7668,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
6384
7668
 
6385
7669
  // src/providers/openai/openai-image.ts
6386
7670
  init_errors();
6387
- import fs37 from "fs";
6388
- import path42 from "path";
7671
+ import fs39 from "fs";
7672
+ import path44 from "path";
6389
7673
  function buildImageGenerationBody(opts) {
6390
7674
  return {
6391
7675
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -6418,14 +7702,14 @@ function saveOpenAIImages(opts) {
6418
7702
  for (let i = 0; i < items.length; i++) {
6419
7703
  const b64 = items[i]?.b64_json;
6420
7704
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
6421
- const dest = path42.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
6422
- fs37.writeFileSync(dest, Buffer.from(b64, "base64"));
7705
+ const dest = path44.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
7706
+ fs39.writeFileSync(dest, Buffer.from(b64, "base64"));
6423
7707
  saved.push(dest);
6424
7708
  opts.logger?.success(`Saved: ${dest}`);
6425
7709
  }
6426
7710
  if (opts.output && saved[0]) {
6427
- fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
6428
- fs37.copyFileSync(saved[0], opts.output);
7711
+ fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
7712
+ fs39.copyFileSync(saved[0], opts.output);
6429
7713
  opts.logger?.success(`Copied to: ${opts.output}`);
6430
7714
  }
6431
7715
  return saved;
@@ -6434,7 +7718,7 @@ async function resolveImageRefBytes(ref) {
6434
7718
  if (isHttpUrl(ref)) {
6435
7719
  const bytes2 = await fetchBytes(ref);
6436
7720
  const pathname = new URL(ref).pathname;
6437
- const filename = path42.basename(pathname) || "reference.png";
7721
+ const filename = path44.basename(pathname) || "reference.png";
6438
7722
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
6439
7723
  }
6440
7724
  const file = fileFromPath(ref);
@@ -6479,7 +7763,7 @@ function sniffImageMime2(buf, name) {
6479
7763
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
6480
7764
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
6481
7765
  return "image/webp";
6482
- const ext = path42.extname(name).toLowerCase();
7766
+ const ext = path44.extname(name).toLowerCase();
6483
7767
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
6484
7768
  if (ext === ".webp") return "image/webp";
6485
7769
  return "image/png";
@@ -6623,8 +7907,8 @@ function registerOpenAICommands(program2) {
6623
7907
  }
6624
7908
 
6625
7909
  // src/providers/openrouter/client.ts
6626
- import fs38 from "fs";
6627
- import path43 from "path";
7910
+ import fs40 from "fs";
7911
+ import path45 from "path";
6628
7912
  init_errors();
6629
7913
 
6630
7914
  // src/providers/openrouter/payload.ts
@@ -6788,8 +8072,8 @@ async function generateOpenRouterImage(opts) {
6788
8072
  if (!url) continue;
6789
8073
  try {
6790
8074
  const bytes = await fetchBytes(url);
6791
- const dest = path43.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
6792
- fs38.writeFileSync(dest, bytes);
8075
+ const dest = path45.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
8076
+ fs40.writeFileSync(dest, bytes);
6793
8077
  savedFiles.push(dest);
6794
8078
  logger?.success(`Saved: ${dest}`);
6795
8079
  } catch (e) {
@@ -6800,8 +8084,8 @@ async function generateOpenRouterImage(opts) {
6800
8084
  }
6801
8085
  }
6802
8086
  if (output && savedFiles[0]) {
6803
- fs38.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
6804
- fs38.writeFileSync(output, fs38.readFileSync(savedFiles[0]));
8087
+ fs40.mkdirSync(path45.dirname(path45.resolve(output)), { recursive: true });
8088
+ fs40.writeFileSync(output, fs40.readFileSync(savedFiles[0]));
6805
8089
  }
6806
8090
  return { status: "success", generatedImages: savedFiles, model: usedModel };
6807
8091
  }
@@ -6842,8 +8126,8 @@ Generated ${finalImages.length} image(s):`);
6842
8126
  }
6843
8127
 
6844
8128
  // src/providers/openrouter/commands/image-to-image.ts
6845
- import fs39 from "fs";
6846
- import path44 from "path";
8129
+ import fs41 from "fs";
8130
+ import path46 from "path";
6847
8131
  init_errors();
6848
8132
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
6849
8133
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
@@ -6935,14 +8219,14 @@ async function runImageToImage(opts) {
6935
8219
  const url = parsed.urls[i];
6936
8220
  if (!url) continue;
6937
8221
  const bytes = await fetchBytes(url);
6938
- const dest = path44.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
6939
- fs39.writeFileSync(dest, bytes);
8222
+ const dest = path46.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
8223
+ fs41.writeFileSync(dest, bytes);
6940
8224
  saved.push(dest);
6941
8225
  opts.logger.success(`Saved: ${dest}`);
6942
8226
  }
6943
8227
  if (opts.output && saved[0]) {
6944
- fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
6945
- fs39.copyFileSync(saved[0], opts.output);
8228
+ fs41.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
8229
+ fs41.copyFileSync(saved[0], opts.output);
6946
8230
  opts.logger.success(`Copied to: ${opts.output}`);
6947
8231
  }
6948
8232
  return saved;
@@ -6987,13 +8271,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
6987
8271
  }
6988
8272
 
6989
8273
  // src/providers/openrouter/commands/video-status.ts
6990
- import fs40 from "fs";
6991
- import path45 from "path";
8274
+ import fs42 from "fs";
8275
+ import path47 from "path";
6992
8276
  async function downloadOpenRouterVideo(opts) {
6993
8277
  const { jobId, logger, source } = opts;
6994
8278
  const apiKey = requireOpenRouterKey();
6995
8279
  const url = videoContentUrl(jobId);
6996
- const dest = path45.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
8280
+ const dest = path47.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
6997
8281
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
6998
8282
  if (!r.ok) {
6999
8283
  logger.error(`Download HTTP ${r.status}`);
@@ -7003,14 +8287,14 @@ async function downloadOpenRouterVideo(opts) {
7003
8287
  logger.error("Empty response body");
7004
8288
  process.exit(1);
7005
8289
  }
7006
- fs40.mkdirSync(path45.dirname(dest), { recursive: true });
7007
- const ws = fs40.createWriteStream(dest);
8290
+ fs42.mkdirSync(path47.dirname(dest), { recursive: true });
8291
+ const ws = fs42.createWriteStream(dest);
7008
8292
  const { pipeline: pipeline2 } = await import("stream/promises");
7009
8293
  await pipeline2(r.body, ws);
7010
8294
  logger.success(`Saved ${dest}`);
7011
8295
  if (opts.output) {
7012
- fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
7013
- fs40.copyFileSync(dest, opts.output);
8296
+ fs42.mkdirSync(path47.dirname(path47.resolve(opts.output)), { recursive: true });
8297
+ fs42.copyFileSync(dest, opts.output);
7014
8298
  logger.success(`Copied to ${opts.output}`);
7015
8299
  }
7016
8300
  await maybeDownloadThumb(source, dest, {
@@ -7039,7 +8323,7 @@ function registerOpenRouterVideoStatusCommand(parent) {
7039
8323
  onTick: (n, v) => logger.debug(`attempt ${n} \u2014 status: ${v.status}`)
7040
8324
  }) : await pollVideoJob(jobId);
7041
8325
  } catch (e) {
7042
- if (e instanceof PollTimeoutError) {
8326
+ if (e instanceof PollTimeoutError2) {
7043
8327
  logger.error(`Timed out waiting for job ${jobId}`);
7044
8328
  process.exit(1);
7045
8329
  }
@@ -7123,7 +8407,7 @@ function registerOpenRouterImageToVideoCommand(parent) {
7123
8407
  onTick: (n, v) => logger.debug(`attempt ${n} \u2014 status: ${v.status}`)
7124
8408
  });
7125
8409
  } catch (e) {
7126
- if (e instanceof PollTimeoutError) {
8410
+ if (e instanceof PollTimeoutError2) {
7127
8411
  logger.error(`Timed out \u2014 multix openrouter video-status ${res.id}`);
7128
8412
  process.exit(1);
7129
8413
  }
@@ -7176,7 +8460,7 @@ function registerOpenRouterCommands(program2) {
7176
8460
  }
7177
8461
 
7178
8462
  // src/commands/check.ts
7179
- import fs41 from "fs";
8463
+ import fs43 from "fs";
7180
8464
  import os4 from "os";
7181
8465
 
7182
8466
  // src/commands/check-helpers/binary-check.ts
@@ -7233,6 +8517,7 @@ To configure multix, set at least one provider API key:
7233
8517
  Leonardo.Ai : https://app.leonardo.ai/settings/api-keys
7234
8518
  BytePlus : https://console.byteplus.com/auth/api-keys
7235
8519
  fal.ai : https://fal.ai/dashboard/keys
8520
+ Atlas Cloud : https://www.atlascloud.ai/console/api-keys
7236
8521
 
7237
8522
  Option A \u2014 user global config (recommended):
7238
8523
  echo 'GEMINI_API_KEY=your-key' >> ~/.multix/.env
@@ -7301,6 +8586,12 @@ var PROVIDERS = [
7301
8586
  envPrimary: "FAL_KEY",
7302
8587
  optional: true,
7303
8588
  link: "https://fal.ai/dashboard/keys"
8589
+ },
8590
+ {
8591
+ name: "Atlas Cloud",
8592
+ envPrimary: "ATLASCLOUD_API_KEY",
8593
+ optional: true,
8594
+ link: "https://www.atlascloud.ai/console/api-keys"
7304
8595
  }
7305
8596
  ];
7306
8597
  function resolveProviderKey(p) {
@@ -7356,7 +8647,7 @@ function registerCheckCommand(program2) {
7356
8647
  }
7357
8648
  if (!legacyClaudeEnvEnabled()) {
7358
8649
  const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os4.homedir();
7359
- const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs41.existsSync(f));
8650
+ const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs43.existsSync(f));
7360
8651
  if (legacy.length > 0) {
7361
8652
  logger.info(
7362
8653
  `Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
@@ -7461,8 +8752,8 @@ function registerCheckCommand(program2) {
7461
8752
 
7462
8753
  // src/commands/doc/convert.ts
7463
8754
  init_errors();
7464
- import fs42 from "fs";
7465
- import path46 from "path";
8755
+ import fs44 from "fs";
8756
+ import path48 from "path";
7466
8757
 
7467
8758
  // src/commands/doc/default-prompt.ts
7468
8759
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -7483,18 +8774,18 @@ async function convertDocuments(opts) {
7483
8774
  const model = opts.model ?? DOC_MODEL_DEFAULT;
7484
8775
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
7485
8776
  for (const f of inputs) {
7486
- if (!fs42.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
8777
+ if (!fs44.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
7487
8778
  }
7488
8779
  let outputPath;
7489
8780
  if (output) {
7490
- outputPath = path46.resolve(output);
8781
+ outputPath = path48.resolve(output);
7491
8782
  } else if (autoName && inputs.length === 1 && inputs[0]) {
7492
- const base = path46.basename(inputs[0], path46.extname(inputs[0]));
7493
- outputPath = path46.join(getOutputDir(), `${base}-extraction.md`);
8783
+ const base = path48.basename(inputs[0], path48.extname(inputs[0]));
8784
+ outputPath = path48.join(getOutputDir(), `${base}-extraction.md`);
7494
8785
  } else {
7495
- outputPath = path46.join(getOutputDir(), "document-extraction.md");
8786
+ outputPath = path48.join(getOutputDir(), "document-extraction.md");
7496
8787
  }
7497
- fs42.mkdirSync(path46.dirname(outputPath), { recursive: true });
8788
+ fs44.mkdirSync(path48.dirname(outputPath), { recursive: true });
7498
8789
  const results = [];
7499
8790
  for (let i = 0; i < inputs.length; i++) {
7500
8791
  const filePath = inputs[i];
@@ -7554,7 +8845,7 @@ async function convertDocuments(opts) {
7554
8845
  `);
7555
8846
  for (const r of results) {
7556
8847
  lines.push(`
7557
- ## ${path46.basename(r.file)}
8848
+ ## ${path48.basename(r.file)}
7558
8849
  `);
7559
8850
  if (r.status === "success" && r.markdown) {
7560
8851
  lines.push(`${r.markdown}
@@ -7567,7 +8858,7 @@ async function convertDocuments(opts) {
7567
8858
  }
7568
8859
  lines.push("\n---\n");
7569
8860
  }
7570
- fs42.writeFileSync(outputPath, lines.join("\n"), "utf8");
8861
+ fs44.writeFileSync(outputPath, lines.join("\n"), "utf8");
7571
8862
  const succeeded = results.filter((r) => r.status === "success").length;
7572
8863
  console.log(`
7573
8864
  ${"=".repeat(50)}`);
@@ -7615,12 +8906,12 @@ function registerDocCommands(program2) {
7615
8906
 
7616
8907
  // src/commands/media/index.ts
7617
8908
  init_errors();
7618
- import fs45 from "fs";
7619
- import path49 from "path";
8909
+ import fs47 from "fs";
8910
+ import path51 from "path";
7620
8911
 
7621
8912
  // src/commands/media/batch.ts
7622
- import fs43 from "fs";
7623
- import path47 from "path";
8913
+ import fs45 from "fs";
8914
+ import path49 from "path";
7624
8915
 
7625
8916
  // src/commands/media/detect-type.ts
7626
8917
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -7816,10 +9107,10 @@ async function batchOptimize(opts) {
7816
9107
  verbose,
7817
9108
  logger
7818
9109
  } = opts;
7819
- fs43.mkdirSync(outputDir, { recursive: true });
7820
- const entries = fs43.readdirSync(inputDir);
9110
+ fs45.mkdirSync(outputDir, { recursive: true });
9111
+ const entries = fs45.readdirSync(inputDir);
7821
9112
  const mediaFiles = entries.filter((f) => {
7822
- const ext = path47.extname(f).toLowerCase();
9113
+ const ext = path49.extname(f).toLowerCase();
7823
9114
  return ALL_EXTS.has(ext);
7824
9115
  });
7825
9116
  if (mediaFiles.length === 0) {
@@ -7830,9 +9121,9 @@ async function batchOptimize(opts) {
7830
9121
  let succeeded = 0;
7831
9122
  const errors = [];
7832
9123
  for (const file of mediaFiles) {
7833
- const inputPath = path47.join(inputDir, file);
7834
- const outputPath = path47.join(outputDir, file);
7835
- const ext = path47.extname(file).toLowerCase();
9124
+ const inputPath = path49.join(inputDir, file);
9125
+ const outputPath = path49.join(outputDir, file);
9126
+ const ext = path49.extname(file).toLowerCase();
7836
9127
  const kind = extToKind(ext);
7837
9128
  logger?.debug(`Processing: ${file}`);
7838
9129
  try {
@@ -7869,8 +9160,8 @@ async function batchOptimize(opts) {
7869
9160
 
7870
9161
  // src/commands/media/split-video.ts
7871
9162
  init_errors();
7872
- import fs44 from "fs";
7873
- import path48 from "path";
9163
+ import fs46 from "fs";
9164
+ import path50 from "path";
7874
9165
  async function splitVideo(opts) {
7875
9166
  await assertFfmpeg();
7876
9167
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -7881,10 +9172,10 @@ async function splitVideo(opts) {
7881
9172
  logger?.info("Video is short enough \u2014 no splitting needed");
7882
9173
  return [input];
7883
9174
  }
7884
- fs44.mkdirSync(outputDir, { recursive: true });
7885
- const ext = path48.extname(input) || ".mp4";
7886
- const baseName = path48.basename(input, ext);
7887
- const outputPattern = path48.join(outputDir, `${baseName}_chunk_%03d${ext}`);
9175
+ fs46.mkdirSync(outputDir, { recursive: true });
9176
+ const ext = path50.extname(input) || ".mp4";
9177
+ const baseName = path50.basename(input, ext);
9178
+ const outputPattern = path50.join(outputDir, `${baseName}_chunk_%03d${ext}`);
7888
9179
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
7889
9180
  const args = [
7890
9181
  "-i",
@@ -7903,8 +9194,8 @@ async function splitVideo(opts) {
7903
9194
  await runFfmpeg(args, { verbose, logger });
7904
9195
  const files = [];
7905
9196
  for (let i = 0; i < numChunks + 1; i++) {
7906
- const fname = path48.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
7907
- if (fs44.existsSync(fname)) files.push(fname);
9197
+ const fname = path50.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
9198
+ if (fs46.existsSync(fname)) files.push(fname);
7908
9199
  }
7909
9200
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
7910
9201
  return files;
@@ -7918,9 +9209,9 @@ function registerMediaCommands(program2) {
7918
9209
  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(
7919
9210
  async (opts) => {
7920
9211
  const logger = createLogger({ verbose: opts.verbose ?? false });
7921
- if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7922
- fs45.mkdirSync(path49.dirname(path49.resolve(opts.output)), { recursive: true });
7923
- const ext = path49.extname(opts.input).toLowerCase();
9212
+ if (!fs47.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
9213
+ fs47.mkdirSync(path51.dirname(path51.resolve(opts.output)), { recursive: true });
9214
+ const ext = path51.extname(opts.input).toLowerCase();
7924
9215
  const kind = extToKind(ext);
7925
9216
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
7926
9217
  const quality = Number.parseInt(opts.quality, 10);
@@ -7960,7 +9251,7 @@ function registerMediaCommands(program2) {
7960
9251
  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(
7961
9252
  async (opts) => {
7962
9253
  const logger = createLogger({ verbose: opts.verbose ?? false });
7963
- if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
9254
+ if (!fs47.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
7964
9255
  const chunks = await splitVideo({
7965
9256
  input: opts.input,
7966
9257
  outputDir: opts.outputDir,
@@ -7976,7 +9267,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
7976
9267
  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(
7977
9268
  async (opts) => {
7978
9269
  const logger = createLogger({ verbose: opts.verbose ?? false });
7979
- if (!fs45.existsSync(opts.inputDir))
9270
+ if (!fs47.existsSync(opts.inputDir))
7980
9271
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
7981
9272
  const result = await batchOptimize({
7982
9273
  inputDir: opts.inputDir,
@@ -8156,6 +9447,7 @@ function registerCommands(program2) {
8156
9447
  registerCloudflareCommands(program2);
8157
9448
  registerElevenLabsCommands(program2);
8158
9449
  registerFalCommands(program2);
9450
+ registerAtlasCloudCommands(program2);
8159
9451
  registerMediaCommands(program2);
8160
9452
  registerDocCommands(program2);
8161
9453
  }
@@ -8169,7 +9461,7 @@ loadEnv();
8169
9461
  var require3 = createRequire2(import.meta.url);
8170
9462
  var pkg = require3("../package.json");
8171
9463
  var program = new Command().name("multix").description(
8172
- "AI multimodal CLI \u2014 image/video/audio/3D generation, editing, transcription, and media processing via OpenAI, Gemini, MiniMax, OpenRouter, Leonardo, BytePlus, ElevenLabs, fal.ai, ffmpeg, and ImageMagick"
9464
+ "AI multimodal CLI \u2014 image/video/audio/3D generation, editing, transcription, and media processing via OpenAI, Gemini, MiniMax, OpenRouter, Leonardo, BytePlus, ElevenLabs, fal.ai, Atlas Cloud, ffmpeg, and ImageMagick"
8173
9465
  ).version(pkg.version);
8174
9466
  registerCommands(program);
8175
9467
  program.parseAsync(process.argv).catch((err2) => {