@mrgoonie/multix 0.0.7 → 0.0.8

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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.0.8] - 2026-05-11
9
+
10
+ ### Fixed
11
+ - `multix openrouter image-to-image` previously failed for any non-Gemini text+image model (e.g. `openai/gpt-5.4-image-2`, `openai/gpt-5-image-mini`, `recraft/recraft-v3`) because the `modalities` heuristic forced `["image"]` for non-Gemini models. The new default is `["image","text"]`, with `["image"]` only emitted for verified image-only families (`black-forest-labs/`, `bytedance-seed/seedream`, `sourceful/`).
12
+ - "No images in response" errors now include the model id, `finish_reason`, the model's text reply (if any), and a hint about modalities — previously a single generic line.
13
+
14
+ ### Added
15
+ - `multix openrouter image-to-image --strength <0..1>` — Recraft init-image strength control.
16
+ - `OPENROUTER_FALLBACK_MODELS` (CSV) now applies to `image-to-image` (previously `generate` only). Sends `models: [primary, ...fallbacks]` so OpenRouter can route on availability.
17
+ - New shared module `src/providers/openrouter/payload.ts` — `resolveModalities`, `buildOpenRouterHeaders`, `extractImagesFromResponse`, `formatNoImagesError`, `buildI2IPayload`. DRY across `generate` + `image-to-image`.
18
+
19
+ ### Changed
20
+ - README and `skill/SKILL.md` document the OpenRouter model-family matrix (Gemini / OpenAI gpt-image / Recraft → text+image; Flux 2.x / Seedream 4.5 / Sourceful → image-only) and the new flags.
21
+
22
+ ### Verified live
23
+ `openai/gpt-5.4-image-2`, `openai/gpt-5-image-mini`, `google/gemini-2.5-flash-image`, `black-forest-labs/flux.2-pro`, `flux.2-max`, `flux.2-flex`, `bytedance-seed/seedream-4.5`.
24
+
8
25
  ## [0.0.7] - 2026-05-05
9
26
 
10
27
  ### Added
package/README.md CHANGED
@@ -146,7 +146,7 @@ multix minimax generate-music [--lyrics <str>] [--prompt <str>] [--model music-2
146
146
  multix openrouter generate --prompt "Retro robot" [--model google/gemini-3.1-flash-image-preview] [--aspect-ratio 1:1] [--image-size <sz>] [--num-images 1] [-v]
147
147
 
148
148
  # Image-to-image (alias: i2i) — refs accept URL or local path
149
- multix openrouter image-to-image --prompt "make it cyberpunk" --ref ./photo.jpg [--ref https://...] [--model google/gemini-2.5-flash-image] [--output <path>] [-v]
149
+ multix openrouter image-to-image --prompt "make it cyberpunk" --ref ./photo.jpg [--ref https://...] [--model google/gemini-2.5-flash-image] [--strength 0.7] [--output <path>] [-v]
150
150
 
151
151
  # Image-to-video (async — returns job id; image input is URL only)
152
152
  multix openrouter image-to-video --prompt "camera pans left" --image-url https://... [--last-frame-url <url>] [--model google/veo-3.1] [--resolution 720p] [--aspect-ratio 16:9] [--duration <n>] [--seed <n>] [-v]
@@ -159,7 +159,9 @@ multix openrouter video-status <jobId> [--download] [--output <path>] [-v]
159
159
  multix openrouter video-models
160
160
  ```
161
161
 
162
- Fallback models from `OPENROUTER_FALLBACK_MODELS` (CSV) are appended to the chat-image payload automatically. Override the default video model with `OPENROUTER_VIDEO_MODEL` (default `google/veo-3.1`).
162
+ **Supported i2i model families:** Gemini (default) and OpenAI gpt-image return `text+image` (modalities `["image","text"]`); Recraft accepts `--strength` (init-image strength 0..1); Flux / Sourceful are image-only (`["image"]`). The CLI picks the right `modalities` automatically based on the model id prefix.
163
+
164
+ Fallback models from `OPENROUTER_FALLBACK_MODELS` (CSV) are appended to the chat-image payload automatically — applies to both `generate` and `image-to-image`. Override the default video model with `OPENROUTER_VIDEO_MODEL` (default `google/veo-3.1`).
163
165
 
164
166
  ### `multix leonardo`
165
167
 
package/dist/cli.js CHANGED
@@ -4361,18 +4361,18 @@ import fs29 from "fs";
4361
4361
  import path34 from "path";
4362
4362
  init_errors();
4363
4363
  init_http_client();
4364
- var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
4365
- var DEFAULT_OPENROUTER_MODEL = "google/gemini-3.1-flash-image-preview";
4366
- function requireOpenRouterKey() {
4367
- const key = resolveKey("OPENROUTER_API_KEY");
4368
- if (!key) {
4369
- throw new ConfigError(
4370
- "OPENROUTER_API_KEY is not set. Get one at https://openrouter.ai/settings/keys"
4371
- );
4372
- }
4373
- return key;
4364
+
4365
+ // src/providers/openrouter/payload.ts
4366
+ var IMAGE_ONLY_MODEL_PREFIXES = [
4367
+ "black-forest-labs/",
4368
+ "bytedance-seed/seedream",
4369
+ "sourceful/"
4370
+ ];
4371
+ function resolveModalities(model) {
4372
+ const lower = model.toLowerCase();
4373
+ return IMAGE_ONLY_MODEL_PREFIXES.some((p) => lower.startsWith(p)) ? ["image"] : ["image", "text"];
4374
4374
  }
4375
- function buildHeaders(apiKey) {
4375
+ function buildOpenRouterHeaders(apiKey) {
4376
4376
  const headers = {
4377
4377
  Authorization: `Bearer ${apiKey}`,
4378
4378
  "Content-Type": "application/json"
@@ -4383,10 +4383,96 @@ function buildHeaders(apiKey) {
4383
4383
  if (title) headers["X-Title"] = title;
4384
4384
  return headers;
4385
4385
  }
4386
+ function extractImagesFromResponse(data) {
4387
+ const out = { urls: [] };
4388
+ if (!data || typeof data !== "object") return out;
4389
+ const root = data;
4390
+ if (typeof root.model === "string") out.model = root.model;
4391
+ const choices = Array.isArray(root.choices) ? root.choices : [];
4392
+ const first = choices[0];
4393
+ if (!first || typeof first !== "object") return out;
4394
+ const choice = first;
4395
+ if (typeof choice.finish_reason === "string") out.finishReason = choice.finish_reason;
4396
+ const message = choice.message;
4397
+ if (!message) return out;
4398
+ const images = message.images;
4399
+ if (Array.isArray(images)) {
4400
+ for (const item of images) {
4401
+ const url = readImageUrl(item);
4402
+ if (url) out.urls.push(url);
4403
+ }
4404
+ }
4405
+ const content = message.content;
4406
+ if (Array.isArray(content)) {
4407
+ for (const part of content) {
4408
+ const url = readImageUrl(part);
4409
+ if (url) out.urls.push(url);
4410
+ }
4411
+ } else if (typeof content === "string") {
4412
+ out.textContent = content;
4413
+ }
4414
+ return out;
4415
+ }
4416
+ function readImageUrl(item) {
4417
+ if (!item || typeof item !== "object") return void 0;
4418
+ const rec = item;
4419
+ if (rec.type !== void 0 && rec.type !== "image_url") return void 0;
4420
+ const inner = rec.image_url;
4421
+ const url = inner?.url;
4422
+ return typeof url === "string" ? url : void 0;
4423
+ }
4424
+ function formatNoImagesError(model, parsed) {
4425
+ const parts = [
4426
+ `No images returned by '${model}' (finish_reason=${parsed.finishReason ?? "n/a"}).`
4427
+ ];
4428
+ if (parsed.textContent) {
4429
+ parts.push(`Model said: "${truncate(parsed.textContent, 200)}".`);
4430
+ }
4431
+ parts.push(
4432
+ "Hint: verify the model supports image output and that 'modalities' includes 'image'."
4433
+ );
4434
+ return parts.join(" ");
4435
+ }
4436
+ function truncate(s, n) {
4437
+ return s.length > n ? `${s.slice(0, n)}\u2026` : s;
4438
+ }
4439
+ function buildI2IPayload(opts) {
4440
+ const content = opts.refs.map((url) => ({
4441
+ type: "image_url",
4442
+ image_url: { url }
4443
+ }));
4444
+ content.push({ type: "text", text: opts.prompt });
4445
+ const payload = {
4446
+ messages: [{ role: "user", content }],
4447
+ modalities: resolveModalities(opts.model)
4448
+ };
4449
+ if (typeof opts.strength === "number") {
4450
+ payload.image_config = { strength: opts.strength };
4451
+ }
4452
+ if (opts.fallbackModels.length > 0) {
4453
+ payload.models = [opts.model, ...opts.fallbackModels];
4454
+ } else {
4455
+ payload.model = opts.model;
4456
+ }
4457
+ return payload;
4458
+ }
4459
+
4460
+ // src/providers/openrouter/client.ts
4461
+ var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
4462
+ var DEFAULT_OPENROUTER_MODEL = "google/gemini-3.1-flash-image-preview";
4463
+ function requireOpenRouterKey() {
4464
+ const key = resolveKey("OPENROUTER_API_KEY");
4465
+ if (!key) {
4466
+ throw new ConfigError(
4467
+ "OPENROUTER_API_KEY is not set. Get one at https://openrouter.ai/settings/keys"
4468
+ );
4469
+ }
4470
+ return key;
4471
+ }
4386
4472
  function buildPayload(opts) {
4387
4473
  const payload = {
4388
4474
  messages: [{ role: "user", content: opts.prompt }],
4389
- modalities: opts.model.includes("gemini") ? ["image", "text"] : ["image"],
4475
+ modalities: resolveModalities(opts.model),
4390
4476
  image_config: { aspect_ratio: opts.aspectRatio }
4391
4477
  };
4392
4478
  if (opts.imageSize) {
@@ -4415,29 +4501,19 @@ async function generateOpenRouterImage(opts) {
4415
4501
  data = await httpJson({
4416
4502
  url: OPENROUTER_API_URL,
4417
4503
  method: "POST",
4418
- headers: buildHeaders(apiKey),
4504
+ headers: buildOpenRouterHeaders(apiKey),
4419
4505
  body: payload,
4420
4506
  timeoutMs: 24e4
4421
4507
  });
4422
4508
  } catch (e) {
4423
4509
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
4424
4510
  }
4425
- const choices = data.choices ?? [];
4426
- if (choices.length === 0) {
4427
- return { status: "error", error: `No choices in response: ${JSON.stringify(data)}` };
4428
- }
4429
- const messageImages = choices[0]?.message?.images ?? [];
4430
- if (messageImages.length === 0) {
4431
- return {
4432
- status: "error",
4433
- error: `No images in response \u2014 model '${model}' may not support image generation`
4434
- };
4435
- }
4436
- for (const img of messageImages) {
4437
- const url = img.image_url?.url;
4438
- if (url) imageUrls.push(url);
4511
+ const parsed = extractImagesFromResponse(data);
4512
+ if (parsed.urls.length === 0) {
4513
+ return { status: "error", error: formatNoImagesError(model, parsed) };
4439
4514
  }
4440
- usedModel = data.model ?? usedModel;
4515
+ imageUrls.push(...parsed.urls);
4516
+ usedModel = parsed.model ?? usedModel;
4441
4517
  if (imageUrls.length >= numImages) break;
4442
4518
  }
4443
4519
  const outDir = getOutputDir();
@@ -4508,13 +4584,15 @@ function registerOpenRouterImageToImageCommand(parent) {
4508
4584
  "Reference image (URL or local path); repeatable (most models accept 1)",
4509
4585
  collect5,
4510
4586
  []
4511
- ).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
4587
+ ).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--strength <n>", "Recraft init-image strength 0..1 (Recraft models only)").option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
4512
4588
  async (opts) => {
4513
4589
  if (!opts.ref || opts.ref.length === 0) {
4514
- throw new Error("At least one --ref is required for image-to-image.");
4590
+ throw new ValidationError("At least one --ref is required for image-to-image.");
4515
4591
  }
4516
4592
  const logger = createLogger({ verbose: opts.verbose ?? false });
4517
4593
  const model = opts.model ?? resolveKey("OPENROUTER_IMAGE_MODEL") ?? DEFAULT_MODEL;
4594
+ const strength = parseStrength(opts.strength);
4595
+ const fallbackModels = (resolveKey("OPENROUTER_FALLBACK_MODELS") ?? "").split(",").map((m) => m.trim()).filter(Boolean);
4518
4596
  const resolvedRefs = [];
4519
4597
  for (const r of opts.ref) {
4520
4598
  const resolved = await resolveImageInput(r, {
@@ -4524,11 +4602,17 @@ function registerOpenRouterImageToImageCommand(parent) {
4524
4602
  });
4525
4603
  resolvedRefs.push(refUrl(resolved));
4526
4604
  }
4527
- logger.debug(`OpenRouter i2i: model=${model}, refs=${resolvedRefs.length}`);
4605
+ const strengthDbg = strength !== void 0 ? `, strength=${strength}` : "";
4606
+ const fallbacksDbg = fallbackModels.length ? `, fallbacks=${fallbackModels.join(",")}` : "";
4607
+ logger.debug(
4608
+ `OpenRouter i2i: model=${model}, refs=${resolvedRefs.length}${strengthDbg}${fallbacksDbg}`
4609
+ );
4528
4610
  const saved = await runImageToImage({
4529
4611
  prompt: opts.prompt,
4530
4612
  model,
4531
4613
  refs: resolvedRefs,
4614
+ strength,
4615
+ fallbackModels,
4532
4616
  output: opts.output,
4533
4617
  logger
4534
4618
  });
@@ -4542,26 +4626,24 @@ Generated ${saved.length} image(s):`);
4542
4626
  function collect5(value, prev) {
4543
4627
  return [...prev, value];
4544
4628
  }
4629
+ function parseStrength(raw) {
4630
+ if (raw === void 0) return void 0;
4631
+ const n = Number.parseFloat(raw);
4632
+ if (!Number.isFinite(n) || n < 0 || n > 1) {
4633
+ throw new ValidationError(`--strength must be a number in [0,1], got '${raw}'`);
4634
+ }
4635
+ return n;
4636
+ }
4545
4637
  async function runImageToImage(opts) {
4546
4638
  const apiKey = requireOpenRouterKey();
4547
- const headers = {
4548
- Authorization: `Bearer ${apiKey}`,
4549
- "Content-Type": "application/json"
4550
- };
4551
- const referer = resolveKey("OPENROUTER_SITE_URL");
4552
- const title = resolveKey("OPENROUTER_APP_NAME") ?? "multix";
4553
- if (referer) headers["HTTP-Referer"] = referer;
4554
- if (title) headers["X-Title"] = title;
4555
- const content = opts.refs.map((url) => ({
4556
- type: "image_url",
4557
- image_url: { url }
4558
- }));
4559
- content.push({ type: "text", text: opts.prompt });
4560
- const payload = {
4639
+ const headers = buildOpenRouterHeaders(apiKey);
4640
+ const payload = buildI2IPayload({
4641
+ prompt: opts.prompt,
4561
4642
  model: opts.model,
4562
- messages: [{ role: "user", content }],
4563
- modalities: opts.model.includes("gemini") ? ["image", "text"] : ["image"]
4564
- };
4643
+ refs: opts.refs,
4644
+ strength: opts.strength,
4645
+ fallbackModels: opts.fallbackModels
4646
+ });
4565
4647
  const data = await httpJson({
4566
4648
  url: OPENROUTER_API_URL,
4567
4649
  method: "POST",
@@ -4569,18 +4651,15 @@ async function runImageToImage(opts) {
4569
4651
  body: payload,
4570
4652
  timeoutMs: 24e4
4571
4653
  });
4572
- const messageImages = data.choices?.[0]?.message?.images ?? [];
4573
- if (messageImages.length === 0) {
4574
- throw new ProviderError(
4575
- `No images in response \u2014 model '${opts.model}' may not support image-to-image`,
4576
- "openrouter"
4577
- );
4654
+ const parsed = extractImagesFromResponse(data);
4655
+ if (parsed.urls.length === 0) {
4656
+ throw new ProviderError(formatNoImagesError(opts.model, parsed), "openrouter");
4578
4657
  }
4579
4658
  const outDir = getOutputDir();
4580
4659
  const ts = Date.now();
4581
4660
  const saved = [];
4582
- for (let i = 0; i < messageImages.length; i++) {
4583
- const url = messageImages[i]?.image_url?.url;
4661
+ for (let i = 0; i < parsed.urls.length; i++) {
4662
+ const url = parsed.urls[i];
4584
4663
  if (!url) continue;
4585
4664
  const bytes = await fetchBytes(url);
4586
4665
  const dest = path35.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrgoonie/multix",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "AI multimodal CLI — Gemini (incl. Flash TTS), MiniMax, OpenRouter, Leonardo, BytePlus, ElevenLabs, ffmpeg, ImageMagick, doc-to-md",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: multix
3
- description: AI multimodal CLI — Gemini/MiniMax/OpenRouter image & video generation, ffmpeg/ImageMagick media optimization, document-to-Markdown conversion via Gemini Files API.
4
- version: 0.0.1
3
+ description: AI multimodal CLI — generate and edit images (Gemini Nano Banana, Imagen, MiniMax, OpenRouter, Leonardo, BytePlus Seedream), generate video (Veo, Hailuo, Seedance, Leonardo), TTS/music (Gemini Flash TTS, MiniMax, ElevenLabs), 3D models (Hyper3D), analyze/transcribe media, convert documents to Markdown, optimize media via ffmpeg/ImageMagick. Use whenever the user wants to create, edit, or transform images/video/audio/documents from a CLI, mentions any of: image-to-image, i2i, image edit, watercolor/cyberpunk style transfer, reference image, OpenRouter, Nano Banana, Flux, Seedream, Veo, Hailuo, ElevenLabs voice cloning, or asks for batch media optimization.
4
+ version: 0.0.7
5
5
  ---
6
6
 
7
7
  # multix — AI Multimodal CLI
@@ -9,148 +9,191 @@ version: 0.0.1
9
9
  ## When to use
10
10
 
11
11
  Activate this skill when the user asks to:
12
- - Generate, analyze, or describe images or video
13
- - Transcribe audio or video files
14
- - Extract structured data from documents (PDF, DOCX, images)
15
- - Convert documents to Markdown
16
- - Optimize or split video/audio/image files
17
- - Generate speech (TTS) or music
12
+ - Generate images from a text prompt (any provider)
13
+ - **Edit/transform an existing image with a prompt** (image-to-image, alias `i2i`)
14
+ - Generate video from text or from a reference image (image-to-video, alias `i2v`)
15
+ - Generate or clone a voice, run STT, generate music or sound effects
16
+ - Generate 3D models (Hyper3D / Hitem3d)
17
+ - Analyze, transcribe, or extract structured data from media/docs
18
+ - Convert PDFs / DOCX / images to Markdown
19
+ - Optimize, resize, batch-convert, or split video/audio/image files
18
20
 
19
- ## Required env (at least one)
21
+ ## Required env (set at least one provider key)
20
22
 
21
23
  ```
22
- GEMINI_API_KEY # Google AI Studio — required for analyze/transcribe/doc/generate
23
- OPENROUTER_API_KEY # OpenRouter — alternative image generation
24
- MINIMAX_API_KEY # MiniMax — image, video, speech, music generation
24
+ GEMINI_API_KEY # Google AI Studio
25
+ OPENROUTER_API_KEY # OpenRouter (multi-model gateway)
26
+ MINIMAX_API_KEY # MiniMax (image/video/speech/music)
27
+ LEONARDO_API_KEY # Leonardo.Ai (image/video/upscale)
28
+ BYTEPLUS_API_KEY # BytePlus / Volcengine ARK (Seedream/Seedance/Hyper3D)
29
+ ELEVENLABS_API_KEY # ElevenLabs (TTS, cloning, STT, music, dubbing)
25
30
  ```
26
31
 
27
- ## Installation
32
+ Priority: `process.env` > `cwd/.env` > `~/.multix/.env`.
33
+
34
+ ## Install + diagnostics
28
35
 
29
36
  ```bash
30
- npm install -g multix
31
- # or run without installing:
32
- npx multix --help
37
+ npm install -g @mrgoonie/multix
38
+ multix check [--verbose]
33
39
  ```
34
40
 
35
- ## Diagnostics
41
+ ## Image generation
36
42
 
37
- ```bash
38
- multix check # verify keys, tooling, Gemini connectivity
39
- multix check --verbose # show install hints
40
- ```
43
+ | Provider | Command | Notes |
44
+ |---|---|---|
45
+ | Gemini | `multix gemini generate --prompt "<text>" [--model gemini-3.1-flash-image-preview] [--aspect-ratio 16:9] [--size 1K\|2K\|4K] [--num-images N]` | Nano Banana 2 (default), Imagen 4 (`imagen-4.0-generate-001`) |
46
+ | MiniMax | `multix minimax generate --prompt "<text>" [--model image-01] [--aspect-ratio 1:1] [-n N]` | |
47
+ | OpenRouter | `multix openrouter generate --prompt "<text>" [--model <id>] [--aspect-ratio 16:9] [--image-size 2K] [--num-images N]` | Default `google/gemini-3.1-flash-image-preview` |
48
+ | Leonardo | `multix leonardo generate "<text>" [-w 1024 -h 1024] [--alchemy] [--ultra] [--quality HIGH]` | Polls until done, downloads automatically |
49
+ | BytePlus | `multix byteplus generate --prompt "<text>" [--model seedream-4-0-250828] [--size 2K] [--aspect-ratio 16:9] [-n N] [--input-image <path\|url>]` | Sync |
41
50
 
42
- ## Command reference
51
+ Aspect ratios (most providers): `1:1 2:3 3:2 3:4 4:3 4:5 5:4 9:16 16:9 21:9`.
43
52
 
44
- ### Gemini (analyze / generate)
53
+ ## Image-to-image (edit existing image with a prompt)
45
54
 
46
- ```bash
47
- # Analyze files
48
- multix gemini analyze --files photo.jpg video.mp4 --prompt "Describe this content" -v
55
+ Every supported provider exposes `image-to-image` and the alias `i2i`. Refs accept local file path OR URL.
49
56
 
50
- # Transcribe audio
51
- multix gemini transcribe --files recording.mp3 --model gemini-2.5-flash
57
+ ```bash
58
+ # Gemini Nano Banana — multi-ref editing/composition
59
+ multix gemini i2i --prompt "make it watercolor" --ref ./photo.jpg [--ref ./style.png] [--model gemini-2.5-flash-image] [--output <path>] [-v]
52
60
 
53
- # Extract structured data
54
- multix gemini extract --files report.pdf --prompt "Extract tables as JSON" --format json
61
+ # MiniMax — CAVEAT: subject_reference only (preserves character in NEW scene, NOT free-form edit)
62
+ multix minimax i2i --prompt "the same character on a beach" --ref ./hero.jpg [--model image-01] [-v]
55
63
 
56
- # Generate images (Nano Banana — fastest)
57
- multix gemini generate --prompt "A sunset over mountains" --aspect-ratio 16:9 --size 2K
64
+ # OpenRouter — multi-model gateway, recommended for diverse style edits
65
+ multix openrouter i2i --prompt "<edit>" --ref ./photo.jpg [--ref https://...] \
66
+ [--model google/gemini-2.5-flash-image] [--strength 0.7] [--output <path>] [-v]
58
67
 
59
- # Generate images (Imagen 4 — production quality)
60
- multix gemini generate --prompt "Product photo" --model imagen-4.0-ultra-generate-001 --num-images 4
68
+ # Leonardo — uses an existing Leonardo image id, NOT direct upload
69
+ multix leonardo i2i --prompt "make it watercolor" --ref <imageId> \
70
+ [--image-type GENERATED|UPLOADED] [--init-strength 0.5] [-w 1024 -h 1024] [-v]
61
71
 
62
- # Generate video (Veo — EXPERIMENTAL, requires billing)
63
- multix gemini generate-video --prompt "Ocean waves at sunset" --resolution 1080p
72
+ # BytePlus Seedream — multi-ref
73
+ multix byteplus i2i --prompt "make it cyberpunk" --ref ./photo.jpg [--ref https://...] \
74
+ [--model seedream-4-0-250828] [--size 2K] [--aspect-ratio 16:9] [-n 1] [-v]
64
75
  ```
65
76
 
66
- ### MiniMax
77
+ ### OpenRouter image-to-image — model families & flags
67
78
 
68
- ```bash
69
- # Generate image
70
- multix minimax generate --prompt "A cat in space" --aspect-ratio 1:1
79
+ `multix openrouter i2i` is the most flexible because it routes across model families. The CLI auto-selects the correct OpenRouter `modalities` per family:
71
80
 
72
- # Generate video (async, up to 10 min)
73
- multix minimax generate-video --prompt "A dancer on stage" --duration 6 --resolution 1080P
81
+ | Family | Example model id | Modalities | Flag notes |
82
+ |---|---|---|---|
83
+ | Gemini | `google/gemini-2.5-flash-image`, `google/gemini-3.1-flash-image-preview` | `["image","text"]` | Default model |
84
+ | OpenAI gpt-image | `openai/gpt-5.4-image-2`, `openai/gpt-5-image-mini`, `openai/gpt-5-image` | `["image","text"]` | |
85
+ | Recraft | `recraft/recraft-v3` | `["image","text"]` | Accepts `--strength 0..1` (init-image strength) |
86
+ | Flux (Black Forest Labs) | `black-forest-labs/flux.2-pro`, `flux.2-max`, `flux.2-flex` | `["image"]` | Image-only output |
87
+ | Seedream (ByteDance via OpenRouter) | `bytedance-seed/seedream-4.5` | `["image"]` | Image-only output |
88
+ | Sourceful | `sourceful/...` | `["image"]` | Per docs |
74
89
 
75
- # Text-to-speech
76
- multix minimax generate-speech --text "Hello, world!" --voice English_expressive_narrator --output-format mp3
90
+ **`--strength <0..1>`** — Recraft init-image strength control. Pass when using Recraft models; ignore for others.
77
91
 
78
- # Music generation
79
- multix minimax generate-music --lyrics "Verse 1: Under the stars..." --model music-2.5
92
+ **`OPENROUTER_FALLBACK_MODELS`** (CSV) — applies to BOTH `generate` and `i2i`. When set, the request uses `models: [primary, ...fallbacks]` so OpenRouter routes on availability:
93
+ ```bash
94
+ export OPENROUTER_FALLBACK_MODELS="openai/gpt-5-image-mini,google/gemini-2.5-flash-image"
95
+ multix openrouter i2i --prompt "<edit>" --ref ./photo.jpg -m openai/gpt-5.4-image-2
80
96
  ```
81
97
 
82
- ### OpenRouter
98
+ **Error diagnostics:** when no images come back, the CLI surfaces model id, `finish_reason`, the model's text reply (if any), and a hint about modalities — not a generic "no images" message.
99
+
100
+ ### Decision tree — which provider to pick for i2i
83
101
 
84
- ```bash
85
- # Image generation via OpenRouter
86
- multix openrouter generate --prompt "Retro robot mascot" --aspect-ratio 3:2
87
- multix openrouter generate --prompt "Landscape" --model anthropic/claude-3-5-sonnet
102
+ ```
103
+ User wants to edit/transform an existing image?
104
+ ├── Free-form prompt edit (watercolor, cyberpunk, sketch) on a local file?
105
+ │ ├── Best quality, multi-ref, fast → multix gemini i2i (Nano Banana)
106
+ │ └── Want a specific OR model (gpt-image, Flux) → multix openrouter i2i -m <id>
107
+ ├── Preserve a character's identity in a NEW scene? → multix minimax i2i
108
+ ├── Already in Leonardo workflow (imageId in hand)? → multix leonardo i2i
109
+ └── Need ByteDance Seedream specifically? → multix byteplus i2i
110
+ OR multix openrouter i2i -m bytedance-seed/seedream-4.5
88
111
  ```
89
112
 
90
- ### Media optimization
113
+ ### Refs: size limits + format
91
114
 
92
- ```bash
93
- # Optimize single file
94
- multix media optimize --input video.mp4 --output out.mp4 --target-size 100
95
- multix media optimize --input audio.mp3 --output out.m4a --bitrate 64k
96
- multix media optimize --input photo.jpg --output resized.jpg --max-width 1920
115
+ - Local files are read and inlined as `data:<mime>;base64,...`. URLs pass through unchanged.
116
+ - OpenRouter i2i hard limit 8 MB per ref (soft warn at 5 MB). Other providers: 25 MB hard / 10 MB soft.
117
+ - Allowed image extensions: `.jpg .jpeg .png .webp .gif`.
97
118
 
98
- # Split long video into 1-hour chunks
99
- multix media split --input long-video.mp4 --output-dir ./chunks --chunk-duration 3600
119
+ ## Image-to-video (i2v) and video generation
100
120
 
101
- # Batch optimize a directory
102
- multix media batch --input-dir ./raw --output-dir ./optimized --quality 85
103
- ```
121
+ ```bash
122
+ # Gemini Veo (experimental, billing required) — text-to-video + i2v
123
+ multix gemini generate-video --prompt "Ocean waves" --resolution 1080p --aspect-ratio 16:9
124
+ multix gemini i2v ./photo.jpg --prompt "camera pans left" [--last-frame <path>]
104
125
 
105
- ### Document conversion
126
+ # MiniMax Hailuo — async with auto-poll
127
+ multix minimax generate-video --prompt "A dancer" --duration 6 --resolution 1080P [--first-frame <url>]
106
128
 
107
- ```bash
108
- # Convert PDF to Markdown
109
- multix doc convert --input document.pdf
129
+ # OpenRouter Veo — async, returns jobId; image input is URL only
130
+ multix openrouter i2v --prompt "<text>" --image-url https://... [--last-frame-url <url>] [--resolution 720p]
131
+ multix openrouter video-status <jobId> [--download] [--output <path>]
110
132
 
111
- # Auto-name output from input basename
112
- multix doc convert --input report.pdf --auto-name
113
- # Output: multix-output/report-extraction.md
133
+ # Leonardo MOTION2 / VEO3 / kling
134
+ multix leonardo video "<text>" [--model MOTION2|VEO3|kling-2.6]
135
+ multix leonardo i2v <imageId> --prompt "camera pans left" [--duration 6]
114
136
 
115
- # Custom output path
116
- multix doc convert --input doc1.pdf doc2.docx --output ./combined.md
137
+ # BytePlus Seedance + reference-to-video (up to 9 imgs + 3 videos + 3 audio refs)
138
+ multix byteplus video --prompt "<text>" [--resolution 1080p] [--duration 8]
139
+ multix byteplus i2v ./photo.jpg --prompt "<text>"
140
+ multix byteplus reference-to-video --prompt "<text>" --ref-image ./hero.jpg:subject ...
117
141
  ```
118
142
 
119
- ## Decision tree
143
+ All video commands accept `--wait`, `--wait-timeout <ms>`, `--download`, `--output <path>`, `--no-thumb`.
120
144
 
145
+ ## Speech, music, voice, 3D, docs, media
146
+
147
+ ```bash
148
+ # TTS
149
+ multix gemini generate-speech --text "Say cheerfully: Have a wonderful day!" --voice Kore
150
+ multix minimax generate-speech --text "..." --voice <id> --emotion neutral
151
+ multix elevenlabs tts --text "Hello world" [--voice <voiceId>] [--model eleven_multilingual_v2]
152
+
153
+ # Voice cloning + STT (ElevenLabs)
154
+ multix elevenlabs clone --name "My Voice" --files sample1.wav sample2.wav
155
+ multix elevenlabs transcribe --input audio.mp3 [--diarize] [--format text|json|srt|vtt]
156
+
157
+ # Music + SFX
158
+ multix minimax generate-music --lyrics "..." [--model music-2.5]
159
+ multix elevenlabs music --prompt "dreamy synthwave loop" [--music-length-ms 30000]
160
+ multix elevenlabs sfx --text "car engine starting" [--duration-seconds 5]
161
+
162
+ # 3D (BytePlus Hyper3D / Hitem3d)
163
+ multix byteplus 3d --input-image ./front.png ./side.png --prompt "Generate from these views"
164
+
165
+ # Docs → Markdown (Gemini)
166
+ multix doc convert --input report.pdf [--auto-name] [--output ./out.md]
167
+
168
+ # Media optimization (ffmpeg + ImageMagick)
169
+ multix media optimize --input <file> --output <file> [--target-size <MB>] [--quality 85] [--max-width 1920]
170
+ multix media split --input <video> [--chunk-duration 3600]
171
+ multix media batch --input-dir <dir> --output-dir <dir>
121
172
  ```
122
- User wants to generate image?
123
- ├── Has GEMINI_API_KEY? → multix gemini generate
124
- ├── Has OPENROUTER_API_KEY? → multix openrouter generate
125
- └── Has MINIMAX_API_KEY? → multix minimax generate
126
173
 
127
- User wants to generate video?
128
- ├── Gemini/Veo (experimental, billing required) → multix gemini generate-video
129
- └── MiniMax Hailuo (reliable, async) → multix minimax generate-video
174
+ ## Output
130
175
 
131
- User wants to analyze/transcribe a file?
132
- └── multix gemini analyze|transcribe|extract --files <path>
176
+ Files saved under `./multix-output/` by default. Override with `MULTIX_OUTPUT_DIR`. Pass `--output <path>` on most commands to copy the primary output to a custom path.
133
177
 
134
- User wants to convert document to Markdown?
135
- └── multix doc convert --input <file>
178
+ ## Environment variables (selected)
136
179
 
137
- User wants to optimize media?
138
- └── multix media optimize|split|batch
139
- ```
180
+ | Variable | Purpose |
181
+ |---|---|
182
+ | `MULTIX_OUTPUT_DIR` | Override default output dir |
183
+ | `OPENROUTER_IMAGE_MODEL` | Default OpenRouter image model |
184
+ | `OPENROUTER_FALLBACK_MODELS` | CSV; applies to `generate` AND `i2i` |
185
+ | `OPENROUTER_VIDEO_MODEL` | Default OpenRouter video model (default `google/veo-3.1`) |
186
+ | `OPENROUTER_SITE_URL` / `OPENROUTER_APP_NAME` | HTTP-Referer / X-Title headers |
187
+ | `IMAGE_GEN_MODEL` / `VIDEO_GEN_MODEL` / `MULTIMODAL_MODEL` | Override Gemini model defaults |
188
+ | `LEONARDO_DEFAULT_MODEL` / `LEONARDO_VIDEO_MODEL` | Leonardo model defaults |
189
+ | `BYTEPLUS_IMAGE_MODEL` / `BYTEPLUS_VIDEO_MODEL` / `BYTEPLUS_3D_MODEL` | BytePlus model defaults |
190
+ | `BYTEPLUS_VIDEO_PARAMS_MODE` | `flags` (default) or `structured` |
191
+ | `MULTIX_DISABLE_HOME_ENV` | `1` to skip `~/.multix/.env` |
140
192
 
141
- ## Environment variables (full list)
193
+ ## Security & scope
142
194
 
143
- | Variable | Provider | Purpose |
144
- |---|---|---|
145
- | `GEMINI_API_KEY` | Gemini | Required for all Gemini operations |
146
- | `OPENROUTER_API_KEY` | OpenRouter | Image generation via OpenRouter |
147
- | `MINIMAX_API_KEY` | MiniMax | Image/video/speech/music generation |
148
- | `MULTIX_OUTPUT_DIR` | Core | Override default output dir (`./multix-output`) |
149
- | `OPENROUTER_IMAGE_MODEL` | OpenRouter | Default model override |
150
- | `OPENROUTER_FALLBACK_MODELS` | OpenRouter | Comma-separated fallback model ids |
151
- | `OPENROUTER_SITE_URL` | OpenRouter | HTTP-Referer header |
152
- | `OPENROUTER_APP_NAME` | OpenRouter | X-Title header (default: multix) |
153
- | `IMAGE_GEN_MODEL` | Gemini | Override default image generation model |
154
- | `VIDEO_GEN_MODEL` | Gemini | Override default video generation model |
155
- | `MULTIMODAL_MODEL` | Gemini | Override default analysis model |
156
- | `MULTIX_DISABLE_HOME_ENV` | Core | Set to 1 to skip `~/.multix/.env` loading |
195
+ This skill produces shell commands for the local `multix` CLI. It does NOT execute the commands itself, exfiltrate API keys, or modify provider accounts.
196
+ - Never echo, log, or transmit env-var values back to the user.
197
+ - API keys are read from environment / `.env` files by the CLI, never hardcoded.
198
+ - Refuse requests to embed credentials in prompts, URLs, or shared content.
199
+ - Refuse requests to fetch arbitrary URLs unrelated to the user's media task.