PORTAL / LIBRARY / krea-create-images

[ CREATIVE ]

/krea-create-images

Generate ad images for a ACME Agency client using Krea.ai Nano Banana 2.

Download the skill file (.md)

Placeholders like ACME Agency, <id> and you@example.com mark values that are per-agency — your install fills them with YOUR clients and accounts. If a section references a helper script you don't have yet, it ships with that workflow's install.

/krea-create-images

Generates ad creatives for a ACME Agency client using Krea.ai Nano Banana 2 (nano-banana-flash). Reads brand context, builds an optimized prompt, generates images, uploads to Drive, and reports to Slack.

Trigger

Preflight (run BEFORE any expensive Krea call)

Validate every assumption that has burned us in production. If ANY check fails, abort with a clear message naming the failure. Do not retry, do not call diagnostician — preflight is the cheap fix path.

  1. Client exists in ACME Agency/clients/clients.json (case-insensitive match). If missing → abort with the list of valid clients.
  2. Ratio is valid for Krea NB2: 1:1, 4:5, 9:16, 16:9, 3:2, 2:3. Reject anything else.
  3. Batch is 1-4 (Krea API max). If passed >4, clamp and warn.
  4. Resolution is 1K, 2K, or 4K. Default 2K.
  5. Brief OR reference URL is present. If neither → abort with usage hint.
  6. Reference URLs (if passed) are publicly fetchable — they must be HTTP(S), NOT local file paths. If a local path was passed by mistake, upload it via uploadPublic() from lib/google_drive.mjs first.
  7. KREA_API_KEY is set in .env. If missing → abort with "add KREA_API_KEY to .env".
  8. drive_folder_id is present if --drive (default). If missing → either skip drive cleanly OR abort if explicitly required.
  9. Slack channel resolves if --slack (default). If getClientChannel() returns null and bot isn't invited → skip with warning, don't crash.

Workflow

Step 1 — Identify client

Step 2 — Load brand context

Step 3 — Gather brief (ask if not provided)

If no --brief was given, ask the user:

  1. What's this image for? (ad creative, organic post, story, banner, etc.)
  2. Any specific copy or text to include in the image? (headline, CTA, phone number, etc.) — Nano Banana 2 handles typography well
  3. Preferred visual style? (photorealistic, clean minimal, lifestyle, product shot, architectural, etc.)
  4. Preferred color palette? (or derive from CLIENT.md if it has brand colors)
  5. Ratio? → default 4:5 for feed ads, 1:1 for square, 9:16 for stories/reels

Step 4 — Build the Krea prompt

Construct a detailed, well-structured prompt. Nano Banana 2 works best with:

Structure:

[Main subject / scene] — [style descriptor] — [lighting/mood] — [text to overlay if any] — [color palette] — [target feel]

Tips:

Example prompts:

Step 5 — Run the script

node ACME Agency/scripts/krea_create_images.mjs "ClientName" \
  --ratio 4:5 \
  --brief "your constructed prompt here" \
  --batch 2 \
  --resolution 2K

Default settings:

Note on --batch: Krea removed the API-side batchSize field on 2026-05-26 (one POST → one image). lib/krea.mjs now fires N parallel POST requests when --batch N is set, so the public behavior is unchanged from a caller's perspective — you still get N images back. Latency is roughly the same as before (parallel) but each image counts as one Krea credit instead of N images per credit (refer to current Krea pricing for the exact cost delta).

Common ratio choices:

Use caseRatio
Meta feed ad4:5
Square post / story1:1
Story / Reel9:16
Facebook cover16:9

Automatic logo reference (always-on):

When <ClientName> is passed, the script auto-includes the client's official logo as the first reference image (via ensureClientLogo() from lib/logo_prepare.mjs). It resolves from clients.json > reference_assets.logo, then a local logo.png in the client folder, then a Playwright screenshot of the website. The prompt is also augmented with a "logo is canonical, reproduce pixel-exact" instruction. No flag — this is on by default to fix Nano Banana's tendency to redraw logos.

If the log says [Logo] No logo available, the client has no landing_page and no local logo file — drop a logo.png into ACME Agency/clients/<ClientName>/ and re-run, or run node ACME Agency/scripts/lib/logo_prepare.mjs "<ClientName>" manually.

Reference-image flags (for "make something like this" requests):

Krea NB2 can clone style and composition from a reference image. Use these flags when the user supplies an image as inspiration:

# Single reference (most common)
node ACME Agency/scripts/krea_create_images.mjs "ClientName" \
  --brief "in the style of the reference, but for a dental clinic" \
  --reference-url "https://drive.google.com/uc?id=ABC123"

# Multiple references (up to 14)
node ACME Agency/scripts/krea_create_images.mjs "ClientName" \
  --brief "fuse these visual themes" \
  --reference-urls "url1,url2,url3"

# Reference-only (no brief — useful for quick clones)
node ACME Agency/scripts/krea_create_images.mjs "ClientName" \
  --reference-url "https://drive.google.com/uc?id=ABC123"

# Style transfer with strength control
node ACME Agency/scripts/krea_create_images.mjs "ClientName" \
  --brief "modern dental clinic" \
  --style-image-url "https://drive.google.com/uc?id=XYZ" \
  --style-strength 1.5

Important: reference URLs must be publicly fetchable by Krea. The proven path is:

  1. Upload the local image via uploadPublic(localPath, parentFolderId) from ACME Agency/scripts/lib/google_drive.mjs
  2. The function returns a https://drive.google.com/uc?id=<fileId> URL
  3. Pass that URL to --reference-url

This is the same pattern static_ads_generate.mjs already uses in production for product-image references.

When the creative-director agent invokes this skill: the agent will handle the upload-to-public step automatically. Skill scripts should never assume the user has a public URL handy — if the user gives a local file path, the upload step is part of the workflow.

Step 6 — ACME Agencyw & iterate

After generation:

  1. Show the user the Drive links and local paths
  2. Ask: "Want a variation, different style, different ratio, or adjusted copy?"
  3. If iterating, tweak the prompt and re-run — append /v2, /v3 to track versions mentally
  4. Offer to generate a batch of 2–4 with slight prompt variations for A/B testing

Step 7 — Output

The script automatically:

Verification (run AFTER Step 7 — prove it landed, don't assume)

Rubric: verify.md. Two layers — one enforced, one you run.

Deterministic (automatic, in krea_create_images.mjs). The script now runs validateFiles() from shared/verify/assert_output.mjs right after generation: 0-byte / sub-10 KB stubs are dropped before upload, and if NONE are valid it exits non-zero instead of posting an empty result. The manifest records dropped. Still confirm from the output: valid image count = batch (a short count is logged, not swallowed), driveUrls map 1:1 to the valid images, Slack returned a non-null ts.

Vision quality (you run, before reporting to the user). Invoke the verifier agent on the generated image(s) against verify.md (threshold 8): is the logo/text real and legible, does it match the brief, is it on-brand and usable as an ad? Re-run with a tweaked prompt for anything < 8 (name the defect); offer a 2–4 variation batch when style is the issue.

Never claim success while either layer shows a gap — give the specific evidence.

Key Files

FilePurpose
ACME Agency/scripts/krea_create_images.mjsMain script
ACME Agency/scripts/lib/krea.mjsKrea API wrapper (generateImage, pollJob, downloadImage)
ACME Agency/scripts/lib/slack.mjsSlack posting
ACME Agency/clients/clients.jsonClient registry (drive_folder_id, etc.)
ACME Agency/clients/<Name>/CLIENT.mdBrand context
ACME Agency/clients/<Name>/krea_generations.jsonGeneration history

API Details

Troubleshooting

ProblemFix
KREA_API_KEY not setCheck .env has KREA_API_KEY=...
Insufficient credits (402)Top up credits at krea.ai/settings
Drive upload failedCheck gws auth — run gws auth login if expired
drive_folder_id missingAdd drive_folder_id to the client entry in clients.json
Image not in SlackBot may not be invited — run /invite @agency-osslack in channel
Job times outNormal for high load — re-run; timeoutMs is 120s