- mb: zero-dependency python CLI for the full API (ops/upload/run/wait/follow/ download incl. folder assets, retry/cancel, settings). Schema-aware -p k=v coercion. Works from any tailnet machine via MB_HOST. Tested end-to-end (upload -> blender_convert -> download). - AGENTS.md: complete handover brief for other agents using this box as an asset factory — endpoints, CLI reference, catalog, recipes, lanes/etiquette. - scripts/serve.sh (headless start/restart) + scripts/install_launchagent.sh (optional boot persistence, owner-run) - flux_local upgraded to mflux 0.18 reality: flux2-klein-4b default (Apache, UNGATED — runs with zero keys), klein-9b, schnell/schnell-4bit community quant, dev/krea-dev. Research verdict: FLUX.1-dev no longer competitive (Elo ~1027) vs klein ~1083-1119 vs nano-banana ~1154. - openrouter_image rewritten to the dedicated Image API (POST /api/v1/images): b64_json parsing, seed, resolution/aspect, exact usage.cost logging; model enum: gemini-2.5-flash-image / 3.1-flash-image (NB2) / 3-pro-image Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--input", action="append", default=[])
|
|
ap.add_argument("--outdir", required=True)
|
|
ap.add_argument("--params", default="{}")
|
|
a = ap.parse_args()
|
|
p = json.loads(a.params)
|
|
|
|
if not p.get("prompt"):
|
|
print("ERROR: prompt is required")
|
|
sys.exit(1)
|
|
|
|
model = p.get("model", "flux2-klein-4b")
|
|
steps = int(p.get("steps", 4))
|
|
outdir = Path(a.outdir)
|
|
out_png = outdir / f"flux_{model.replace('/', '_')}_s{p.get('seed', 42)}.png"
|
|
bindir = Path(sys.executable).parent
|
|
|
|
if model.startswith("flux2-"):
|
|
cli = bindir / "mflux-generate-flux2"
|
|
model_args = ["--model", model]
|
|
quant_args = [] # klein is small; skip quantization flags
|
|
guidance_args = [] # distilled klein: guidance fixed at 1.0
|
|
elif model == "schnell-4bit":
|
|
cli = bindir / "mflux-generate"
|
|
# ungated community pre-quantized weights (~10GB) — no HF license wall
|
|
model_args = ["--model", "dhairyashil/FLUX.1-schnell-mflux-4bit",
|
|
"--base-model", "schnell"]
|
|
quant_args = []
|
|
guidance_args = []
|
|
else:
|
|
cli = bindir / "mflux-generate"
|
|
model_args = ["--model", model]
|
|
quant = str(p.get("quantize", "8"))
|
|
quant_args = ["--quantize", quant] if quant in ("4", "8") else []
|
|
guidance_args = (["--guidance", str(p.get("guidance", 3.5))]
|
|
if model in ("dev", "krea-dev") else [])
|
|
|
|
if not cli.exists():
|
|
print(f"ERROR: mflux not installed ({cli} missing). Run scripts/install_mflux.sh")
|
|
sys.exit(1)
|
|
|
|
cmd = [str(cli), *model_args,
|
|
"--prompt", p["prompt"],
|
|
"--steps", str(steps),
|
|
"--width", str(p.get("width", 1024)),
|
|
"--height", str(p.get("height", 1024)),
|
|
"--seed", str(p.get("seed", 42)),
|
|
"--output", str(out_png),
|
|
*quant_args, *guidance_args]
|
|
|
|
print("+", " ".join(cmd), flush=True)
|
|
print("(first run per model downloads weights from HuggingFace)", flush=True)
|
|
t0 = time.time()
|
|
res = subprocess.run(cmd, stdout=sys.stdout, stderr=subprocess.STDOUT)
|
|
if res.returncode != 0:
|
|
print("HINT: gated-repo/auth errors mean this model needs an HF license accept "
|
|
"+ token (Settings → HuggingFace token). flux2-klein-4b and schnell-4bit "
|
|
"are ungated and need nothing.")
|
|
sys.exit(res.returncode)
|
|
elapsed = round(time.time() - t0, 1)
|
|
|
|
if not out_png.exists():
|
|
pngs = sorted(outdir.glob("*.png"))
|
|
if not pngs:
|
|
print("ERROR: no image produced")
|
|
sys.exit(1)
|
|
out_png = pngs[-1]
|
|
|
|
(outdir / "result.json").write_text(json.dumps({"outputs": [
|
|
{"path": out_png.name,
|
|
"meta": {"tool": "mflux", "model": model, "steps": steps,
|
|
"seed": p.get("seed", 42), "seconds": elapsed}}]}))
|
|
print(f"done in {elapsed}s: {out_png.name}", flush=True)
|