Skip to content
gershamPublic

About

LLM Image Gen CLI Tool

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

curds

to complement your fries and gravy

curds in action

Generate images from the command line. The default image model is Nano Banana 2.1 (nano-banana-2.1; Replicate google/nano-banana-2.1, OpenRouter google/gemini-nano-banana-2.1). GPT Image 2.5 Flare stays available with -model gpt-image-2.5 (direct OpenAI, Replicate, or OpenRouter). Also generates images/videos/music/speech via Replicate-hosted models such as Seedance 2.5 and Grok Imagine Video 1.5 (video), Kling 3.0, ElevenLabs Music (-model music), MiniMax Music 2.6 (-model music-vocal), Stable Audio 2.5 (-model sfx) for sound effects, Gemini 3.1 Flash TTS (-model tts), MiniMax Speech 2.8 HD (-model tts-minimax) and ElevenLabs v3 (-model tts-elevenlabs) for text-to-speech (OpenAI's gpt-4o-mini-tts via -model tts-openai; with an ElevenLabs key, -model tts-elevenlabs and -model music call the ElevenLabs API directly), and the talking-head pair Kling Avatar 2.0 (-model kling-avatar) and Sync Labs lipsync-2-pro (-model lipsync). Also wraps Replicate's bria/remove-background for one-shot transparent-PNG cutouts (-model remove-bg) and prunaai/p-image-upscale / nightmareai/real-esrgan / topazlabs/image-upscale for super-resolution upscaling (-model upscale / upscale-esrgan / upscale-pro), and curds run OWNER/MODEL key=value … drives any other Replicate model with raw inputs. Logs upstream progress in colorized logfmt. When prompt or token is missing curds clears the screen and drops into a Bubble Tea TUI with a CURDS banner, multiline prompt, spinner, scrolling log panel, and a "generate another?" loop after each render.

Requirements

  • Go 1.26+ for building (curds uses generics, structured logging, and recent stdlib helpers; older toolchains will refuse to compile). Install via go.dev/dl, Homebrew (brew install go), asdf, or your distro's package manager.
  • macOS, Linux, or Windows. -open uses open on macOS, xdg-open on Linux, and cmd /C start on Windows.
  • A terminal that supports ANSI colors and Unicode box drawing for the best TUI experience (any modern terminal qualifies — iTerm2, Alacritty, Kitty, Windows Terminal, GNOME Terminal, etc.).
  • An API token from at least one provider:
    • OpenAI — needs API Organization Verification to call gpt-image-2.5.
    • OpenRouter — mapped image, video, and speech models.
    • ElevenLabs — direct v4 speech, account voices, and music.
    • Replicate — alternative backend, no verification dance.
    • xAI — native Grok Imagine Video (recommended for video; cheaper than the Replicate wrapper).
  • Network access to api.openai.com, api.replicate.com, and/or api.x.ai, openrouter.ai, and/or api.elevenlabs.io (plus replicate.delivery / vidgen.x.ai for downloading rendered assets).

Install

./install.sh                  # builds and installs to /usr/local/bin or ~/.local/bin

Or build manually:

go build -o curds ./cmd/curds

Versioning

Curds uses CalVer tags in the form v0.YYYYMMDD.N: the UTC release date followed by a zero-based same-day release number. For example, v0.20261008.0 is the first release on October 8, 2026; another release that day would be v0.20261008.1. The v0 prefix keeps the current Go module path compatible with Go's version rules.

go install github.com/gersham/curds/cmd/curds@v0.20261010.0
curds -version

Earlier tags such as v0.8.0 remain available. See the changelog and release process.

First run

curds -prompt "a watercolor fox in a meadow"

Or with no flags at all to land in the TUI. On first run curds writes a default config to ~/.config/curds/config.toml and saves output to ~/Desktop/curds/<unix_milli>.webp for images, or .mp4 for video models.

Agent skill

skill/ ships a standalone Claude Code / agent skill that teaches an agent to drive the curds CLI: assemble the right flags, run one command, verify the artifact, and report — without rewriting the prompt or launching extra jobs. It's self-contained (SKILL.md + reference.md, no external references), so installing curds and installing the skill go together.

If you install curds for use by a coding agent, also install this skill — copy the directory into your skills location and the agent will know how to call curds for images, edits, video, and background removal:

# Skip this if you already have a `curds` skill installed — don't clobber it.
test -d ~/.claude/skills/curds || cp -R skill ~/.claude/skills/curds

~/.claude/skills/curds is the per-user Claude Code location; adjust the destination for your agent runtime. The guard above leaves any pre-existing curds skill untouched.

Interactive mode (TUI)

Triggered when prompt or token is missing (suppress with -no-tui).

  • Clears the screen and renders the CURDS banner.
  • Captures provider+token via a one-shot form when none is configured; optionally writes the token back to the config file.
  • Multiline prompt input. ctrl+d submits, ctrl+c quits.
  • During generation: spinner + a bordered, scrolling log panel showing every upstream event live (generation.started, openai.request, image.received, …).
  • After generation: shows the saved path(s) and elapsed time. Press g to clear the prompt and generate again, q to quit.

Tokens

Resolution priority (first non-empty wins):

  1. -token flag
  2. [tokens] section of ~/.config/curds/config.toml
  3. .env in the current working directory
  4. process environment (OPENAI_API_KEY, REPLICATE_API_TOKEN, XAI_API_KEY, ELEVENLABS_API_KEY, OPENROUTER_API_KEY)

If no token is found at startup, curds opens the TUI and offers to capture one and (optionally) save it to the config.

The ElevenLabs key is optional. Add it to the config:

[tokens]
elevenlabs = "sk_..."

or export ELEVENLABS_API_KEY (or put it in .env). With it, -model tts-elevenlabs and -model music run on the ElevenLabs API instead of Replicate; see ElevenLabs direct.

Providers

Curds selects the provider per model: a native OpenAI, ElevenLabs, or xAI key first, then Replicate, then OpenRouter for a mapped model. ElevenLabs speech prefers v4: direct ElevenLabs → OpenRouter → Replicate v3, so a configured Replicate key never displaces available v4. For example, with OpenRouter + Replicate + ElevenLabs keys, GPT Image 2.5 and FLUX/video use Replicate, Gemini 3.8 speech uses OpenRouter, and ElevenLabs speech/music use ElevenLabs directly. Gemini speech also prefers 3.8 on OpenRouter over the older 3.1 Replicate fallback. Leave provider = "" to enable this selection.

-provider openai|openrouter|replicate|xai|elevenlabs pins a route. A nonempty config provider also pins it; the CLI flag wins. An incompatible provider and model fail locally. When MP4 output has no explicit model, curds uses default_video_model (seedance-2.5) with Replicate or OpenRouter; native Grok is the fallback when only an xAI video key is available.

Recommended baseline: OpenRouter + Replicate. Add an ElevenLabs key for custom/account voices, voice discovery, and direct music. V4 speech is already available through OpenRouter; the ElevenLabs key is optional for it.

provider = ""                   # choose automatically for each model
[tokens]
openrouter = "YOUR_OPENROUTER_KEY"
replicate = "YOUR_REPLICATE_KEY"
# Optional for account voices and direct speech/music:
# elevenlabs = "YOUR_ELEVENLABS_KEY"

Capabilities by configured keys

These are the automatically selected routes with only the column's keys configured and no provider pin. OR = OpenRouter, Rep = Replicate, EL = ElevenLabs; — = unavailable with those keys.

Generation / method OpenRouter only Replicate only OpenRouter + Replicate OpenRouter + Replicate + ElevenLabs
GPT Image 2.5 Flare / Sunburst OR Rep Rep Rep
GPT Image 2, FLUX.2/3, Nano Banana 2/2.1; reference edits OR Rep Rep Rep
Seedance, Kling, MiniMax H3, Grok 1.5 video OR Rep Rep Rep
Native Grok video model OR — OR OR
Gemini speech (tts) OR 3.8 Rep 3.1 OR 3.8 OR 3.8
MiniMax speech OR Rep Rep Rep
ElevenLabs speech OR v4 Rep v3 OR v4 EL v4
Music (music) — Rep Rep EL
Songs with lyrics / sound effects — Rep Rep Rep
Avatars / lip-sync — Rep Rep Rep
Background removal / upscaling — Rep Rep Rep
ElevenLabs account voices / voice lookup — — — EL
Raw inputs (curds run) — Rep Rep Rep

OpenRouter + Replicate covers every generation type in the table. OpenAI speech and masked image edits require an additional OpenAI key. An xAI key optionally enables the direct native Grok route. Provider-specific limits still apply; see the complete model mappings and route limits.

Provider Default model Endpoint
openai gpt-image-2.5 (gpt-image-2.5-flare; Sunburst via -model gpt-image-2.5-sunburst); speech: gpt-4o-mini-tts (-model tts-openai), tts-1-hd /v1/images/generations (or /v1/images/edits with -input-image); /v1/audio/speech for TTS
openrouter image: google/gemini-nano-banana-2.1; mapped image, video, and speech models, including ElevenLabs v4 POST /api/v1/images, POST /api/v1/videos + polling/content, POST /api/v1/audio/speech
replicate image: google/nano-banana-2.1; video: bytedance/seedance-2.5 (bytedance/seedance-2.0 via -model seedance-2; talking heads: kwaivgi/kling-avatar-v2, sync/lipsync-2-pro); audio: elevenlabs/music, minimax/music-2.6, stability-ai/stable-audio-2.5; speech: google/gemini-3.1-flash-tts, minimax/speech-2.8-hd, elevenlabs/v3; upscale: prunaai/p-image-upscale, nightmareai/real-esrgan, topazlabs/image-upscale /v1/models/<owner>/<name>/predictions (sync via Prefer: wait); curds run posts raw inputs to the same endpoint
xai video: grok-imagine-video POST /v1/videos/generations + GET /v1/videos/{request_id} (async polling)
elevenlabs speech: eleven_v4 (-tts-model to change); music: music_v2_5. Picked automatically for -model tts-elevenlabs / -model music when an elevenlabs token resolves POST /v1/text-to-speech/{voice_id}, POST /v1/music; curds voices reads GET /v2/voices and GET /v1/shared-voices

ElevenLabs is never auto-detected as the general provider: it serves only those two models, and -provider replicate keeps them on Replicate even when the key is set.

Editing / composing with reference images

Pass -input-image (repeatable or comma-separated, up to 16). curds switches to OpenAI's /v1/images/edits endpoint, OpenRouter's input_references, or the model's Replicate reference field automatically. JPEG inputs are transparently re-encoded as PNG for the OpenAI edits endpoint — some camera JPEGs (e.g. straight off an iPhone) are otherwise rejected with invalid_image_file.

# Compose from two references
curds -input-image body-lotion.png,bath-bomb.png \
      -prompt "Relax & Unwind gift basket"

# Mask-driven inpainting (OpenAI only; mask must be PNG with alpha)
curds -input-image lounge.png -mask mask.png \
      -prompt "indoor lounge with flamingo in pool"

Image models

Nano Banana 2.1 is the default (nano-banana-2.1). It routes to Replicate, then OpenRouter. Select -model gpt-image-2.5 for Flare, or sunburst for the larger GPT Image 2.5 model. Both GPT Image 2.5 models work through OpenAI, Replicate, and OpenRouter. -provider openai without -model uses Flare, because OpenAI does not serve Nano Banana. -model flux-3 is explicit. The nano-banana-2, flux-2-pro, and gpt-image-2 names keep their original versions. An existing config file keeps its default_model.

Model Resolution flag Reference images Notes
nano-banana-2.1 -image-resolution 1k/2k/4k 14 Current Google image/editing model; PNG/JPEG.
flux-3 -image-resolution 768sq/1k/1.5k/2k/4k 10 Separate FLUX.3 contract; PNG/JPEG/WebP.
gpt-image-2.5, sunburst -size WxH 16 OpenAI supports custom sizes; Replicate accepts its enumerated size set.

For a generated transparent asset, use GPT Image 2.5 with -background transparent -output-format png (WebP also supports alpha). JPEG is rejected for transparent output. Masked edits require direct OpenAI.

For quality-first recommendations, current model gaps, and a live comparison of six image models, see the October 2026 model evaluation.

gpt-image-2.5 (-model gpt-image-2.5, OpenAI id gpt-image-2.5-flare) is fast everyday GPT Image 2.5. Sunburst (-model gpt-image-2.5-sunburst or -model sunburst, OpenAI id gpt-image-2.5-sunburst) is the larger 2.5 model — higher quality, longer generation. Both take the same request surface as gpt-image-2 — the same -size / -aspect-ratio grid, -quality (low / medium / high / xhigh / max / auto), -background, -moderation, -output-format and -output-compression. gpt-image-2 (-model gpt-image-2) remains selectable for the previous generation's look.

Two Replicate-hosted models cover cases the OpenAI models handle poorly:

-model Use it for
flux-2-pro Cheap, fast bulk generation with reference-image control (~$0.015/gen)
nano-banana-2 Character consistency across frames, multi-image compositing, cheap 4K

Both size their output differently from gpt-image-2, so they share a flag: -image-resolution — 0.5mp, 1mp, 2mp, 4mp, or match_input_image for FLUX.2; 1k, 2k, or 4k for Nano Banana 2.

# FLUX.2 [pro], 2 megapixels, 16:9
curds -model flux-2-pro -aspect-ratio 16:9 -image-resolution 2mp \
      -prompt "a watercolor fox in a meadow" -output /tmp/fox.png

# Nano Banana 2, 4K composite from two references
curds -model nano-banana-2 -image-resolution 4k \
      -input-image hero.png,logo.png \
      -prompt "hero shot with the logo on the bottle" -output /tmp/comp.png

# FLUX.2 with an explicit pixel size (edges 256-2048)
curds -model flux-2-pro -size 1024x768 -prompt "a brass astrolabe"

Both take reference images through -input-image (not -reference-image), produce one image per request (-number-of-images must be 1), and ignore -quality, -background, and -moderation. Neither accepts -mask. FLUX.2 supports -seed; Nano Banana 2 does not. Nano Banana 2 emits PNG or JPEG only — curds switches a webp default to PNG for it, retargeting the -output extension if needed.

Video generation

For MP4 output with no -model, curds uses default_video_model — Seedance 2.5 (bytedance/seedance-2.5, provider replicate), which leads on reference handling (30 images / 10 videos / 10 audios), multi-scene continuity, native audio, and clips up to 30s. Set default_video_model = "seedance-2" or pass -model seedance-2 when you need 1080p — the one resolution 2.5 dropped. When no replicate token is available but an xai token is, curds falls back to xAI's native Grok Imagine Video. Kling 3.0, MiniMax H3, and the Grok 1.5 wrapper are selectable with -model.

Seedance 2.5 via Replicate (seedance-2.5) — default

# Text-to-video (default; no input image required)
curds -prompt "a cinematic 20 second shot of a glass sculpture forming" \
      -aspect-ratio 16:9 -video-duration 20 -output /tmp/seedance.mp4

Seedance-specific support:

  • -input-image — one entry is the first frame; multiple become reference images (up to 30 total with -reference-image).
  • -last-frame-image — requires exactly one -input-image.
  • -reference-video / -reference-audio — up to 10 each.
  • -video-duration — -1 for intelligent duration, or 4 through 30 seconds. Default: 5.
  • -video-resolution — 480p or 720p. Default: 720p. 2.5 has no 1080p; -model seedance-2 and -model kling-v3 do.
  • -no-audio — disables synchronized generated audio.
  • -seed — optional deterministic seed.
  • -aspect-ratio — 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, or adaptive (2.5 dropped 2.0's 9:21). Default: 16:9.

Seedance 2.0 via Replicate (seedance-2)

The previous Seedance, still selectable. Same request surface, a narrower reference set (9 images, 3 videos, 3 audios), durations of -1 or 4-15 seconds, 9:21, and 1080p via -video-resolution 1080p.

Kling 3.0 via Replicate (kling-v3)

Text-to-video or image-to-video with start/end frames, plus native audio with lip-synced dialogue. -video-resolution maps onto Kling's mode: 720p → standard, 1080p → pro, 4k → 4k.

curds -model kling-v3 -input-image start.png -last-frame-image end.png \
      -prompt "the character turns and speaks to camera" \
      -video-resolution 1080p -video-duration 10 -output /tmp/kling.mp4

Kling 3.0 supports:

  • -input-image — optional start frame (0 or 1).
  • -last-frame-image — optional end frame; requires one -input-image.
  • -video-duration — 3 through 15 seconds. Default: 5.
  • -video-resolution — 720p, 1080p, or 4k. Default: 1080p.
  • -aspect-ratio — 16:9, 9:16, or 1:1. Default: 16:9.
  • -no-audio — disables Kling's generated audio (on by default in curds).

No reference images/videos/audio and no -seed — use seedance-2 for those.

Native xAI (grok-imagine-video)

Used automatically for MP4 when no replicate token is available. The native API is roughly half the Replicate cost at 720p and supports text-to-video (image optional), reference images, 480p/720p, and durations up to 15s. It is asynchronous: curds submits the job, polls GET /v1/videos/{request_id}, then downloads the rendered MP4. Audio is generated automatically.

# Text-to-video (no input image)
curds -prompt "a slow serene time-lapse of the milky way" -output /tmp/sky.mp4

# Image-to-video from a reference image, 720p, 10s
curds -provider xai -input-image still.png \
      -prompt "a smooth product turn with soft studio camera motion" \
      -video-resolution 720p -video-duration 10 -output /tmp/xai.mp4

Native grok-imagine-video supports:

  • -input-image — optional image-to-video source (0 or 1).
  • -reference-image — additional reference image(s).
  • -video-duration — 1 through 15 seconds. Default: 5.
  • -video-resolution — 480p or 720p. Default: 720p.
  • -aspect-ratio — auto, 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, or 2:3. auto is omitted from the request so the API derives it (from the source image, for image-to-video). Default: auto.

1080p is not supported by this native model. Curds rejects it before a request; it never silently lowers resolution or switches models. Choose -provider replicate -model seedance-2 -video-resolution 1080p explicitly when that model suits the job. xAI documents native 1080p for the distinct grok-imagine-video-1.5 model, not this adapter (provider resolution documentation).

MiniMax H3 via Replicate (minimax-h3)

H3 is multimodal: text-to-video, first- and/or last-frame image-to-video, and reference-guided generation from images, videos, or audio. 4–15s at 768P ($0.08/s) or 2K ($0.13/s).

# Text-to-video
curds -model minimax-h3 -prompt "a slow serene time-lapse of the milky way" \
      -output /tmp/sky.mp4

# First-frame image-to-video, 2K, 10s
curds -model minimax-h3 -input-image still.png \
      -prompt "a smooth product turn with soft studio camera motion" \
      -video-resolution 2k -video-duration 10 -output /tmp/h3.mp4

MiniMax H3 supports:

  • -input-image — optional first frame (0 or 1) → first_frame_image.
  • -last-frame-image — optional last frame; requires one -input-image.
  • -reference-image — up to 9 reference images.
  • -reference-video — up to 3 reference videos.
  • -reference-audio — up to 3 reference audio clips.
  • -video-duration — 4 through 15 seconds. Default: 5.
  • -video-resolution — 768p or 2k (case-insensitive). Default: 768p.
  • -aspect-ratio — adaptive, 21:9, 16:9, 4:3, 1:1, 3:4, or 9:16. Default: 16:9. There is no auto; image-to-video uses adaptive.

H3 has no -seed and no audio toggle (-no-audio is rejected); use -strip-audio to get a silent clip.

Grok Imagine Video 1.5 via Replicate (grok-imagine-video-1.5)

The Replicate wrapper is image-to-video only, so pass exactly one -input-image. Selectable via -provider replicate -model grok-imagine-video-1.5.

curds -provider replicate -model grok-imagine-video-1.5 -input-image product.png \
      -prompt "a smooth product turn with soft studio camera motion" \
      -output /tmp/grok.mp4

Grok Imagine Video 1.5 supports:

  • -video-duration — 1 through 15 seconds. Default: 5.
  • -video-resolution — 480p or 720p. Default: 720p.
  • -aspect-ratio — auto, 16:9, 4:3, 1:1, 9:16, 3:4, 3:2, or 2:3. Default: auto unless overridden with -aspect-ratio.

In prompts, refer to Seedance references as [Image1], [Video1], [Audio1], etc.

Talking heads and lip-sync (Replicate)

Two Replicate models whose input is media rather than a prompt:

  • kling-avatar — Kling Avatar 2.0 (kwaivgi/kling-avatar-v2). One portrait (-input-image) plus one audio clip (-audio: mp3, wav, m4a, aac) become a lip-synced talking head, ~5 minutes per render. -prompt is optional (actions/emotion/camera); -video-resolution maps onto its mode — 720p → std, 1080p → pro (the default). Pro renders 1080p.
  • lipsync — Sync Labs lipsync-2-pro (sync/lipsync-2-pro). Re-animates the mouth in an existing video (-input-video, mp4) to match new audio (-audio, wav). No prompt. Optional -sync-mode (loop, bounce, cut_off, silence, remap; default loop), -sync-temperature (0–1, default 0.5), and -active-speaker.
# Animate a portrait with an audio clip
curds -model kling-avatar -input-image portrait.png -audio voice.mp3 \
      -prompt "she smiles and explains the product" -output /tmp/avatar.mp4

# Lip-sync an existing clip
curds -model lipsync -input-video clip.mp4 -audio voice.wav \
      -sync-mode loop -output /tmp/lipsync.mp4

Notes:

  • -output-format is forced to mp4; -aspect-ratio is ignored (the portrait or source video defines the frame).
  • -strip-audio defaults to false for these two models — the generated audio is the point. Pass -strip-audio explicitly to silence a clip.
  • -crop-captions crops the result to the top 74% of the frame (centered horizontally), removing the caption band some avatar/lip-sync models burn into the bottom. ffmpeg re-encodes the video at crf 16 and copies the audio; without ffmpeg it logs a skip and leaves the file alone.
  • -no-fallback disables Seedance's automatic face-rejection retry (see below); it is irrelevant for these models.

Seedance face rejection → Kling fallback

Both Seedance versions (bytedance/seedance-2.5 and bytedance/seedance-2.0) reject any input image containing a realistic human face (flagged as sensitive (E005)), where Kling 3.0 accepts the same frames. When a Seedance prediction fails that way and the request carried an image (-input-image, -last-frame-image, or -reference-image), curds retries once on Kling 3.0: same prompt, first image as start_image, last frame as end_image, duration clamped to 3–15s, -video-resolution mapped (480p/720p → standard, 1080p → pro, 4k → 4k), and -aspect-ratio passed through when Kling supports it (else 16:9). The retry is logged as event=model.fallback from=… to=… reason=seedance_rejected_face.

Pass -no-fallback to disable it; curds then returns the original error and logs hint="retry with -model kling-v3".

Music and sound effects

Three Replicate audio models are wired in, all prompt-driven, saving mp3 or wav:

  • music — ElevenLabs Music (elevenlabs/music), the default music model. A score, cue, or loop from -prompt, instrumental by default (-instrumental=false for vocals), and it honors the requested length exactly: -duration 5–300s (default 10) goes upstream as music_length_ms. With an ElevenLabs key it runs on the ElevenLabs API instead (music_v2_5, -duration 3–600s); see ElevenLabs direct.
  • music-vocal — MiniMax Music 2.6 (minimax/music-2.6, alias minimax-music). A full song with vocals. -prompt sets the style; -lyrics TEXT or -lyrics @song.txt supplies the words (newlines and [Verse]/[Chorus] tags pass through), and -lyrics also works with no -prompt. Without lyrics the model writes them from the prompt.
  • sfx — Stable Audio 2.5 (stability-ai/stable-audio-2.5). Sound effects, ambience, and short music cues, 1–190s (-duration, default 10), with an optional -seed.
# Score: a 45-second instrumental cue with an exact length
curds -model music -duration 45 \
      -prompt "tense minimal synth score, slow build, no drums" -output /tmp/score.mp3

# Song with lyrics read from a file
curds -model music-vocal -lyrics @song.txt -duration 60 \
      -prompt "warm indie-folk duet, acoustic guitar and brushed drums" -output /tmp/song.mp3

# 8-second ambience sound effect
curds -model sfx -duration 8 \
      -prompt "steady heavy rain on a tin roof, distant thunder" -output /tmp/rain.wav

Notes:

  • -output-format is mp3 by default for audio, or wav when -output ends in .wav. An mp3/wav output without an audio -model is rejected locally.
  • MiniMax Music 2.6 ignores the requested length upstream (it renders 2–3 minutes), so when -duration is set curds trims the downloaded render with a 2s fade-out via ffmpeg (event=audio.trimmed). Without ffmpeg it logs audio.trim_skipped and keeps the full render.
  • Stable Audio 2.5 picks its own container. When it differs from the requested one, curds transcodes via ffmpeg if available, and otherwise saves the container that came back under its own extension with an audio.format_mismatch warning.
  • -lyrics is music-vocal only; anywhere else it is a usage error. -aspect-ratio, -size, -quality, -background, and -moderation are not sent to audio models.
  • -seed is supported by sfx only.

Text to speech

TTS models read -prompt (or stdin) aloud, all saving mp3 or wav:

  • tts — Gemini 3.8 Flash TTS through OpenRouter when configured, with Gemini 3.1 Flash TTS on Replicate as the fallback. -voice selects a preset (default Kore), and -instructions steers delivery. Curds retains a conservative 4000-byte cap for both text and instructions. OpenRouter returns MP3; the Replicate 3.1 route returns WAV. Container conversion uses ffmpeg when the requested format differs.
  • Explicit Gemini versions are tts-gemini-3.8, tts-gemini-3.8-lite (OpenRouter), and tts-gemini-3.1 (Replicate/OpenRouter). A pinned version never silently falls back to another version.
  • tts-minimax — MiniMax Speech 2.8 HD (minimax/speech-2.8-hd). Natural narration from up to 10000 characters (with <#0.5#> pause markers). -voice takes any system voice id or a cloned id (default English_Wiselady); -emotion (default auto), -speed (0.5–2), and -pitch (−12..12) tune the delivery. curds asks for 44.1 kHz output (256 kbps mp3) and turns on English normalization so numbers and dates read naturally.
  • tts-elevenlabs — ElevenLabs v4 whenever accessible: direct ElevenLabs first, OpenRouter second, and Replicate v3 only when neither v4 route has a key (or Replicate is explicitly selected). V4 interprets audio tags such as [whispers] and supports -stability / -similarity. On OpenRouter, -voice is a preset name (default george); direct ElevenLabs accepts account voice IDs/names (default George). The Replicate v3 fallback uses its own voice enum (default Rachel), supports -style / -speed, and reads bracketed markers aloud. See ElevenLabs direct.
  • tts-openai — OpenAI gpt-4o-mini-tts, served by the openai provider's POST /v1/audio/speech. -instructions steers accent and tone ("crisp British RP, dry"); -voice picks from the OpenAI enum (default sage). tts-1-hd is the earlier model on the same endpoint and takes no -instructions. Text is capped at 4096 characters.
# Gemini: 3.8 on OpenRouter, with 3.1 on Replicate as fallback
curds -model tts -voice Kore -instructions "warm and slow, British accent" \
      -prompt "The results, I'm afraid, are conclusive." -output /tmp/line.wav

# MiniMax narration line with a voice and emotion
curds -model tts-minimax -voice English_Deep-VoicedGentleman -emotion calm \
      -prompt "Chapter one. The rain had not stopped for a week." -output /tmp/line.mp3

# ElevenLabs v4 automatically: direct ElevenLabs, then OpenRouter
curds -model tts-elevenlabs -stability 0.4 \
      -prompt "[warmly] Welcome back." -output /tmp/welcome.mp3

# Explicit legacy v3 on Replicate (bracketed markers are spoken aloud)
curds -provider replicate -model tts-elevenlabs -voice Rachel -style 0.8 \
      -prompt "Oh, brilliant. Another Monday." -output /tmp/line.mp3

# OpenAI with delivery instructions
curds -model tts-openai -voice sage -instructions "crisp British RP, dry" \
      -prompt "The results, I'm afraid, are conclusive." -output /tmp/line.wav

# Read a whole script from a file
cat script.txt | curds -model tts-minimax -output /tmp/narration.wav

Notes:

  • tts prefers OpenRouter 3.8, then Replicate 3.1; tts-minimax prefers Replicate, then OpenRouter. tts-elevenlabs uses direct ElevenLabs v4, then OpenRouter v4, then Replicate v3. tts-openai/tts-1-hd need an OpenAI token. An explicit incompatible -provider/-model pair is rejected locally.
  • -voice is validated against the selected route's voices. Direct ElevenLabs and MiniMax accept account/cloned IDs; OpenRouter ElevenLabs uses preset names, and Replicate ElevenLabs uses its legacy voice enum.
  • Flags that do not apply to the chosen model are usage errors (exit 2): -emotion/-pitch belong to tts-minimax, -stability/-style to tts-elevenlabs, and -instructions to tts and tts-openai.
  • -speed has a per-model range: tts-minimax 0.5–2, Replicate ElevenLabs v3 0.7–1.2, tts-openai 0.25–4. ElevenLabs v4 and Gemini have no speed knob; use v4 audio tags or Gemini -instructions. 0 means model default.
  • List MiniMax voices with curds run -schema minimax/speech-2.8-hd, and Gemini voices with curds run -schema google/gemini-3.1-flash-tts.

Gemini voices: Achernar, Achird, Algenib, Algieba, Alnilam, Aoede, Autonoe, Callirrhoe, Charon, Despina, Enceladus, Erinome, Fenrir, Gacrux, Iapetus, Kore, Laomedeia, Leda, Orus, Pulcherrima, Puck, Rasalgethi, Sadachbia, Sadaltager, Schedar, Sulafat, Umbriel, Vindemiatrix, Zephyr, Zubenelgenubi. ElevenLabs voices: Rachel, Drew, Clyde, Paul, Aria, Domi, Dave, Roger, Fin, Sarah, James, Jane, Juniper, Arabella, Hope, Bradford, Reginald, Gaming, Austin, Kuon, Blondie, Priyanka, Alexandra, Monika, Mark, Grimblewood. OpenAI voices: alloy, ash, ballad, coral, echo, fable, onyx, nova, sage, shimmer, verse, marin, cedar.

ElevenLabs direct

With an ElevenLabs key ([tokens] elevenlabs in the config, .env, or ELEVENLABS_API_KEY), -model tts-elevenlabs and -model music call the ElevenLabs API directly. Without that key, speech uses OpenRouter v4 if available, then Replicate v3; music uses Replicate. -provider replicate explicitly selects the Replicate route. Every run logs the route it took:

event=elevenlabs.route route=direct model=eleven_v4 reason="elevenlabs token"
event=elevenlabs.route route=openrouter model=elevenlabs/eleven-v4 reason="selected OpenRouter route"
event=elevenlabs.route route=replicate model=elevenlabs/v3 reason="no elevenlabs token"

Text to speech (POST /v1/text-to-speech/{voice_id}):

  • -tts-model picks the ElevenLabs model_id. The default is eleven_v4, ElevenLabs' highest-quality model. eleven_v4_turbo, eleven_v3, eleven_multilingual_v2, and eleven_flash_v2_5 are also available.
  • -voice is any voice id (your account's, a premade, or one from the shared library), or the name of a voice in your account, resolved through GET /v2/voices. Premade names carry a tagline ("George - Warm, Captivating Storyteller"), so -voice George matches; an exact full name wins. The default is George (JBFqnCBsd6RMkjVDRZzb). Rachel and the rest of the Replicate enum are legacy voices that current accounts no longer list, so -voice Rachel fails here with a pointer to curds voices. Using a library voice id makes ElevenLabs add that voice to your account.
  • Audio tags are interpreted on eleven_v4 and eleven_v3: [whispers], [laughs], [sighs], [strong French accent] direct the delivery instead of being read aloud. Replicate's elevenlabs/v3 reads them aloud, so the same script sounds different on the two routes.
  • Voice settings depend on the model: -stability works everywhere; -similarity everywhere but eleven_v3; -style and -speaker-boost only on eleven_multilingual_v2; -speed (0.7–1.2) on eleven_multilingual_v2 and eleven_flash_v2_5. A setting the model does not take is a usage error that names -tts-model eleven_multilingual_v2. -instructions, -emotion, and -pitch do not apply.
  • Text caps: eleven_v4 10000 characters, eleven_v3 5000.
  • mp3 asks for mp3_44100_192 and steps down to mp3_44100_128 when the plan refuses it (192 kbps needs the Creator tier). wav asks for wav_44100 (Pro tier), then wav_24000. Each step logs event=elevenlabs.format_fallback.

Music (POST /v1/music): model_id music_v2_5, -prompt, force_instrumental from -instrumental (default true), and music_length_ms from -duration (3–600s; omit it and the model picks the length). mp3 asks for mp3_48000_320, then 192 kbps, then mp3_44100_128. The endpoint has no wav, so a .wav output is transcoded locally via ffmpeg.

Finding a voice: curds voices lists your account's voices (GET /v2/voices) and searches the shared library (GET /v1/shared-voices), one logfmt line per voice with source, id, name, accent, age, gender, and description. -search, -accent, -gender, and -age filter both lists server-side; -source account|library picks one; -limit sets the page size (default 30, max 100).

# Find a British female narrator, then use her
curds voices -search narrator -accent british -gender female
curds -model tts-elevenlabs -voice 3XD5qTvPWht0mpTnYXY0 -stability 0.4 \
      -prompt "[whispers] It was a very long day." -output /tmp/line.mp3

# eleven_multilingual_v2 for the full set of voice settings
curds -model tts-elevenlabs -tts-model eleven_multilingual_v2 -voice George \
      -style 0.3 -speed 1.1 -speaker-boost -prompt "Chapter one." -output /tmp/ch1.mp3

# Ten minutes of score on the direct route
curds -model music -duration 600 -prompt "slow ambient synth score" -output /tmp/score.mp3

# Keep the Replicate route even with a key
curds -provider replicate -model tts-elevenlabs -voice Rachel -prompt "Hi." -output /tmp/hi.mp3

Raw Replicate passthrough (curds run)

curds run creates one prediction on any Replicate model with exactly the inputs you give it — no field mapping, no curds defaults:

curds run OWNER/MODEL[:VERSION] key=value ...

# What does this model accept?
curds run -schema sync/lipsync-2-pro

# Upload local media, read the output URL instead of downloading
curds run -json owner/model prompt="blend these" ref=@a.png ref=@b.png

# Name the output (default: <output.directory>/<unix_milli>.<ext>)
curds run -output /tmp/out.mp4 owner/model prompt="a slow pan"

Values are sent as JSON: key=5, key=true, key=[1,2], key={"a":1}, and key="quoted" are sent as that JSON value; anything that does not parse is a plain string; key=@path uploads a local file as a data URL (http(s) and data: URLs pass through). Repeat a key to send an array: ref=@a.png ref=@b.png. One value holding a comma-separated list is not split — use JSON or repeat the key.

Flags: -output, -schema (print name, type, default, enum, min/max, and description for each input, then exit), -json (print the final prediction JSON instead of downloading; also applied automatically when the output has no URLs, e.g. a text or blob output), -token, -poll-interval, -timeout, -verbose. Multiple outputs get -1, -2, … before the extension. Saved paths go to stdout; tokens resolve through the same chain as the main command. See curds run -h.

Background removal / segmentation

-model remove-bg runs bria/remove-background (BRIA RMBG 2.0) on Replicate. It takes one input image and returns a transparent PNG with a soft 256-level alpha matte — the original pixels are preserved, only the background is removed. There's no prompt, no aspect ratio, and no -quality knob; the output dimensions match the input image.

curds -provider replicate -model remove-bg \
      -input-image photo.jpg -output cutout.png

Notes:

  • Requires exactly one -input-image (file path, http(s) URL, or data: URL).
  • Output format is forced to PNG so the alpha channel survives.
  • bria/remove-background is an official Replicate model — no version pin.
  • For dedicated mask outputs (rather than a finished cutout) you can also pass any other Replicate model identifier directly via -model owner/name, but only bria/remove-background is wired into the validation/format shortcuts.

Upscaling / super-resolution

-model upscale runs prunaai/p-image-upscale (2026-era) on Replicate. It takes one input image and a -scale factor (1–8, default 4), multiplying each side, and returns a single upscaled PNG. Like background removal there's no prompt, no aspect ratio, and no -quality knob — and no face enhancement: use -model upscale-esrgan or -model upscale-pro for that. -output-format accepts png (the default), jpeg, or webp.

# Upscale 4x → PNG
curds -provider replicate -model upscale \
      -input-image small.jpg -scale 4 -output big.png

# Same upscaler, WebP out
curds -provider replicate -model upscale -output-format webp \
      -input-image small.jpg -scale 2 -output big.webp

Notes:

  • Requires exactly one -input-image (file path, http(s) URL, or data: URL).
  • Output format defaults to PNG; -output-format jpeg writes .jpg bytes.
  • -scale accepts 1–8; the default is 4.
  • -face-enhance is a usage error (exit 2) here.
  • prunaai/p-image-upscale is an official Replicate model — no version pin.

Real-ESRGAN (upscale-esrgan)

nightmareai/real-esrgan is the 2021-era upscaler, still selectable as -model upscale-esrgan. It keeps the numeric -scale factor (1–10, default 4) and is the one upscaler with GFPGAN face restoration, which helps on portraits and low-resolution faces.

# Upscale a portrait with face enhancement
curds -provider replicate -model upscale-esrgan -face-enhance \
      -input-image headshot.jpg -output headshot-4x.png
  • One -input-image, no prompt, PNG out.
  • -face-enhance runs GFPGAN alongside the upscale.
  • nightmareai/real-esrgan is an official Replicate model — no version pin.

Topaz (upscale-pro)

Real-ESRGAN dates from 2021 and goes soft on faces and text. -model upscale-pro runs topazlabs/image-upscale instead, which is materially better on both.

curds -model upscale-pro -input-image small.jpg -scale 4 -output big.png

# Portrait with face enhancement
curds -model upscale-pro -input-image headshot.jpg -scale 2 -face-enhance
  • -scale accepts 2, 4, or 6 — omit it for enhance-only (no resize).
  • -face-enhance requires a -scale; Topaz applies it during the upscale pass.
  • Same shape as upscale otherwise: one -input-image, no prompt, PNG out.

Aspect ratios

-aspect-ratio accepts these named ratios (mapped to multiples-of-16 sizes for gpt-image-2 / gpt-image-2.5):

Ratio OpenAI size Notes
1:1 1024×1024
3:2 / 2:3 1536×1024 / 1024×1536
4:3 / 3:4 1536×1152 / 1152×1536
16:9 2048×1152 ~1080p+ landscape
9:16 1152×2048 ~1080p+ portrait
21:9 / 9:21 2688×1152 / 1152×2688 ultrawide
2:1 / 1:2 2048×1024 / 1024×2048
16:9-4k / 9:16-4k 3840×2160 / 2160×3840

Replicate's gpt-image-2 wrapper accepts only 1:1, 3:2, 2:3. Grok Imagine Video 1.5 accepts auto, 16:9, 4:3, 1:1, 9:16, 3:4, 3:2, and 2:3. Native xAI grok-imagine-video accepts auto, 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, and 2:3. Seedance 2.5 accepts 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, and adaptive; Seedance 2.0 also accepts 9:21. Kling 3.0 accepts 16:9, 9:16, and 1:1. FLUX.2 [pro] accepts match_input_image, 1:1, 16:9, 3:2, 2:3, 4:5, 5:4, 9:16, 3:4, 4:3 — or -size WxH with edges 256–2048. Nano Banana 2 accepts match_input_image, 1:1, 1:4, 1:8, 2:3, 3:2, 3:4, 4:1, 4:3, 4:5, 5:4, 8:1, 9:16, 16:9, 21:9. MiniMax H3 accepts 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, and adaptive.

Music, sound-effect, and text-to-speech models take no -aspect-ratio (audio has no frame); -size and -quality are not sent either.

For something custom, pass -size WxH. Anything not on a 16-pixel boundary is rounded to the nearest valid value (true 1080p 1920×1080 becomes 1920×1088, for example). ~/.config/curds/config.toml (auto-created):

provider = ""                   # auto; ElevenLabs speech prefers available v4
default_model = "nano-banana-2.1"   # Replicate google/nano-banana-2.1; OpenRouter google/gemini-nano-banana-2.1
default_video_model = "seedance-2.5"            # model used for MP4 output

[output]
directory = "~/Desktop/curds"
format = "webp"
compression = 90

[tokens]
openai = ""
replicate = ""
xai = ""
elevenlabs = ""                 # optional: direct ElevenLabs TTS and music
openrouter = ""                 # optional: mapped image/video/speech models

[defaults]
quality = "auto"
aspect_ratio = "1:1"
background = "auto"
moderation = "auto"
number_of_images = 1

[models.gpt-image-2.5]
openai_name = "gpt-image-2.5-flare"
openrouter_name = "openai/gpt-image-2.5-flare"

[models.gpt-image-2.5-sunburst]
openai_name = "gpt-image-2.5-sunburst"

[models.gpt-image-2]
openai_name = "gpt-image-2"
replicate_name = "openai/gpt-image-2"

[models.flux-2-pro]
replicate_name = "black-forest-labs/flux-2-pro"

[models.nano-banana-2]
replicate_name = "google/nano-banana-2"

[models.minimax-h3]
replicate_name = "minimax/h3"

[models.kling-v3]
replicate_name = "kwaivgi/kling-v3-video"

[models.kling-avatar]
replicate_name = "kwaivgi/kling-avatar-v2"

[models.lipsync]
replicate_name = "sync/lipsync-2-pro"

[models.upscale-pro]
replicate_name = "topazlabs/image-upscale"

[models.grok-imagine-video]
xai_name = "grok-imagine-video"

[models.grok-imagine-video-1.5]
replicate_name = "xai/grok-imagine-video-1.5"

[models.seedance-2.5]
replicate_name = "bytedance/seedance-2.5"

[models.seedance-2]
replicate_name = "bytedance/seedance-2.0"

[models.remove-bg]
replicate_name = "bria/remove-background"

[models.upscale]
replicate_name = "prunaai/p-image-upscale"

[models.upscale-esrgan]
replicate_name = "nightmareai/real-esrgan"

Override the path with $CURDS_CONFIG.

Logging

Every stage emits a logfmt event to stderr. On a TTY events are colorized; piped output stays plain logfmt for log collectors. Add -verbose for debug-level events (request bodies, etc.).

ts=2026-04-25T17:34:00.123Z level=info  event=curds.start version=0.2.0
ts=2026-04-25T17:34:00.140Z level=info  event=config.loaded path=…
ts=2026-04-25T17:34:00.180Z level=info  event=generation.started provider=openai size=2048x1152 prompt_chars=42
ts=2026-04-25T17:34:00.181Z level=info  event=openai.request endpoint=… kind=generations
ts=2026-04-25T17:34:08.420Z level=info  event=openai.response status_code=200 bytes=987432
ts=2026-04-25T17:34:08.421Z level=info  event=image.received index=0 bytes=987432 format=webp
ts=2026-04-25T17:34:08.435Z level=info  event=curds.completed images=1 duration_ms=8312 paths=…

Upstream errors are captured at level=error:

ts=… level=error event=openai.api_error status_code=400 body="{ \"error\": { … } }"

CLI flags

Run curds -h for the full list. Highlights:

  • -prompt — prompt text (or pipe via stdin, or use the TUI)
  • -output — output path (default: ~/Desktop/curds/<ms>.<format>)
  • -aspect-ratio / -size — image dimensions
  • -quality / -output-format / -output-compression
  • -input-image — reference image(s), repeatable or comma-separated
  • -mask — mask file for OpenAI edits
  • -video-duration / -video-resolution — video controls
  • -no-audio — Seedance-only audio control
  • -audio / -input-video — talking-head and lip-sync media inputs
  • -sync-mode / -sync-temperature / -active-speaker — lipsync-2-pro knobs
  • -crop-captions — crop the bottom caption band off a generated video
  • -no-fallback — disable the Seedance → Kling 3.0 face-rejection retry
  • curds run OWNER/MODEL key=value … — raw Replicate passthrough (-schema, -json)
  • -reference-image / -reference-video / -reference-audio — Seedance references
  • -duration / -instrumental / -lyrics — audio controls (-model music, music-vocal, sfx); -lyrics reads @file.txt
  • -voice / -emotion / -speed / -pitch / -instructions / -stability / -style — text-to-speech controls (-model tts, tts-minimax, tts-elevenlabs, tts-openai)
  • -tts-model / -similarity / -speaker-boost — direct ElevenLabs route only (-model tts-elevenlabs with an ElevenLabs key)
  • curds voices [-search TEXT] [-accent A] [-gender G] [-age A] — list account voices and search the ElevenLabs voice library
  • -scale / -face-enhance — upscale factor and face restoration (-model upscale-esrgan / upscale-pro; -face-enhance is rejected by -model upscale)
  • -provider, -token, -model
  • -open — open generated assets in OS viewer (macOS Preview)
  • -verbose — debug-level logs
  • -no-tui — never enter interactive mode (fail instead)

Layout

.
├── cmd/curds/main.go      package main — CLI shell
├── cmd/curds/voices.go    package main — the `curds voices` subcommand
├── client.go              package curds — Client, Request, Result, providers
├── openai.go              ↑ OpenAI Image API impl
├── replicate.go           ↑ Replicate API impl
├── elevenlabs.go          ↑ ElevenLabs API impl (direct TTS, music, voices)
├── log.go                 ↑ shared logfmt + lipgloss formatter
├── curds_test.go          ↑ unit + httptest integration tests
├── config/                package config — TOML, .env, token resolution
├── tui/                   package tui  — huh-driven interactive form
├── skill/                 standalone agent skill (SKILL.md + reference.md)
├── install.sh             builds + installs to /usr/local/bin or ~/.local/bin
└── go.mod                 module github.com/gersham/curds

License

Personal project. No license attached yet.

About

LLM Image Gen CLI Tool

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages