[ CREATIVE ]
/static-ad-generator
Full pipeline for generating up to 40 production-ready static ads for a ACME Agency client using Krea.ai Nano Banana 2.
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./static-ad-generator
Full static ad creation pipeline for ACME Agency clients: Brand DNA → Template selection → Krea.ai batch generation. Uses 40 proven ad format templates. Inspects existing client images from Drive for style continuity. Caches Brand DNA on Drive so returning clients skip research.
vs /krea-create-images: Use krea-create-images for a single one-off image. Use this skill for a full batch creative run (8–40 ads at once).
With /copywrite: For new client campaigns, run /copywrite meta "ClientName" first to generate and approve ad copy. Then run /static-ad-generator "ClientName" --phase 2 — Brand DNA is already cached, so it skips Phase 1 and goes straight to template selection using the approved copy as Mode A input.
Triggers
/static-ad-generator ACME Agency— full pipeline (Phases 1+2+3)/static-ad-generator ACME Agency --phase 2— re-generate prompts only (uses cached Brand DNA)/static-ad-generator ACME Agency --refresh— force re-run Brand DNA even if Drive copy exists/static-ad-generator ACME Agency --templates 1,3,11— generate specific templates only/static-ad-generator ACME Agency --batch 2— 2 images per template (default: 1)
Preflight (run BEFORE any expensive Krea batch)
A static-ad batch is the most expensive operation in this workspace (8-40 Krea calls × ~48 compute units each). Validate everything BEFORE spending credits. If ANY check fails, abort with the specific failure — do not proceed.
- Client exists in
ACME Agency/clients/clients.json. Resolve the canonical key (case-insensitive). CLIENT.mdexists atACME Agency/clients/<Client>/CLIENT.md. If missing → recommend/paradox-onboardfirst, do NOT proceed.brand-dna.mdexists OR Phase 1 is set to build it. If--phase 2+was passed but no brand-dna.md exists → abort with "run Phase 1 first".static_ads_prompts.jsonexists at the client folder if--phase 3(skip generation) was passed. Otherwise it'll be built in Phase 2.drive_folder_idis present and reachable. Test with alistFiles()call before starting Phase 3 — Drive auth has expired in production before. Fail-fast beats failing 30 minutes into a batch.- Krea API key set in
.env. If missing → abort. - Batch × templates × resolution stays under sane caps:
batch * templates ≤ 80and resolution1K|2K|4K. If higher → require explicit confirmation in the prompt context. - Reference assets resolve if
reference_assetsis set in clients.json: every named tag must point to a real Drive URL. If any fail → abort with the missing tag name. - Slack channel resolves if Slack reporting is enabled. Skip cleanly if bot not invited (don't crash).
- Disk space check: confirm at least 500 MB free in the local client folder. Krea downloads + manifest can blow past quotas on small drives.
If all checks pass, log "preflight: OK (n templates × m batch = X images, ratio Y, est cost Z compute units)" and proceed.
Phase 1 — Brand DNA
Goal: Build a visual identity document that informs every prompt. Check Drive for a cached copy first to avoid redundant research.
Step 1.0 — Check Drive for existing Brand DNA
Before doing any research:
- Look up the client in
ACME Agency/clients/clients.json— getdrive_folder_idand Slack channel - Run:
gws drive files list --folder <drive_folder_id>and look forbrand-dna.md - If found and
--refreshwas NOT passed:
- Download it:
gws drive files download <file_id> --output "ACME Agency/clients/<ClientName>/brand-dna.md" - Tell the user: "Found Brand DNA from [date in file] — skipping research. Use
--refreshto rebuild it." - Skip directly to Phase 2
- If not found or
--refreshwas passed: Proceed with Steps 1.1–1.4 below
Step 1.1 — Load client context
- Read
ACME Agency/clients/<ClientName>/CLIENT.md— extract: business description, products/services, target audience, brand tone, colors, market, unique selling points - If CLIENT.md is missing, note it and rely on web research + user's brief
Step 1.1b — Website scraping with Firecrawl
Goal: Get brand copy, colors, messaging, and visual style from the live website.
- Get the website URL from CLIENT.md or the
notesfield inclients.json - Scrape with Firecrawl (handles JS-rendered pages, returns clean markdown):
node --input-type=module <<'EOF'
import { scrapeWithMeta } from './ACME Agency/scripts/lib/firecrawl.mjs';
const result = await scrapeWithMeta('<WEBSITE_URL>');
console.log('Title:', result.title);
console.log('Description:', result.description);
console.log('OG Image:', result.ogImage);
console.log('Content:\n', result.markdown.slice(0, 3000));
EOF
- Extract from the content:
- Brand messaging: headlines, taglines, value propositions, CTAs
- Services/products: what they offer, how they describe it
- Tone of voice: formal/casual, direct/aspirational, language style
- Visual cues mentioned: any color/style hints in the copy
- Also take a Playwright screenshot if you need exact hex colors for Brand DNA:
node --input-type=module <<'EOF'
import { chromium } from 'playwright';
const b = await chromium.launch();
const p = await b.newPage();
await p.setViewportSize({ width: 1280, height: 900 });
try { await p.goto('https://<WEBSITE_URL>', { waitUntil: 'networkidle', timeout: 15000 }); } catch {}
await p.screenshot({ path: 'ACME Agency/clients/<ClientName>/website-screenshot.png' });
await b.close();
EOF
Then Read the screenshot with Claude vision for exact hex colors, logo style, and font weight.
If the website is unreachable: fall back to web research, note "colors approximate — verify before publishing" in Brand DNA.
Step 1.1c — Logo & reference assets
The logo is auto-prepared on every Phase 3 run by static_ads_generate.mjs, which calls ensureClientLogo() from lib/logo_prepare.mjs. Resolution order: cached clients.json > reference_assets.logo → local logo.png in the client folder → Playwright screenshot from landing_page (or website URL in notes). The result is uploaded to Drive (public) and written back to reference_assets.logo automatically. The logo is then prepended to every template's reference images and the prompt is augmented with a "logo is canonical, reproduce pixel-exact" instruction.
You only need to intervene when the Phase 3 log says [Logo] No logo available. That means the client has no landing_page and no local logo.png. Drop a logo.png (or jpg/webp) into ACME Agency/clients/<ClientName>/ and re-run, or run the helper manually:
node ACME Agency/scripts/lib/logo_prepare.mjs "<ClientName>" # idempotent (cache hit if already set)
node ACME Agency/scripts/lib/logo_prepare.mjs "<ClientName>" --force # re-capture (use after a rebrand)
Then identify what other named reference assets this client has (interior, product, team, hero shot, etc.) and upload them from gallery-tmp/ to clients.json:
node --input-type=module <<'EOF'
import { uploadPublic } from './ACME Agency/scripts/lib/google_drive.mjs';
const folderId = '<drive_folder_id>';
const asset1 = await uploadPublic('ACME Agency/clients/<C>/gallery-tmp/<filename>', folderId);
console.log(asset1);
EOF
"reference_assets": {
"logo": "https://drive.google.com/uc?id=...",
"<tag2>": "https://drive.google.com/uc?id=...",
"<tag3>": "https://drive.google.com/uc?id=..."
}
Name each asset by what it shows (e.g. "exterior", "product", "team", "interior", "hero-shot") — the tag names are referenced in reference_tags in the prompts JSON. Use names that describe the content, not the client-specific subject.
Why this matters: Named assets are passed directly to Krea as reference images per-template instead of a random product-images fallback. Krea sees the real logo and places it naturally where it fits in the design — no forced overlay.
Step 1.2 — Drive Galerija inspection
Check the client's Drive folder for existing creative images — these become style inspiration for the Brand DNA.
Uses google_drive.mjs (no gws CLI needed):
// Step 1: list all files/subfolders in the client Drive folder
node --input-type=module <<'EOF'
import { listFiles, downloadFile } from './ACME Agency/scripts/lib/google_drive.mjs';
import { mkdir } from 'fs/promises';
const FOLDER_ID = '<drive_folder_id>'; // from clients.json
const CLIENT = '<ClientName>';
// Find Galerija subfolder (also matches 'Galerija', 'Gallery', 'galeria')
const all = await listFiles(FOLDER_ID);
const gallery = all.find(f =>
f.mimeType.includes('folder') &&
/galeri|gallery/i.test(f.name)
);
if (!gallery) { console.log('No gallery folder found'); process.exit(0); }
console.log('Gallery folder:', gallery.name, gallery.id);
// List images inside Galerija
const images = await listFiles(gallery.id, { mimeType: 'image' });
console.log(`Found ${images.length} images`);
// Download up to 5 most recent
await mkdir(`ACME Agency/clients/${CLIENT}/gallery-tmp`, { recursive: true });
for (const img of images.slice(0, 5)) {
const ext = img.name.split('.').pop() || 'jpg';
const dest = `ACME Agency/clients/${CLIENT}/gallery-tmp/${img.name}`;
await downloadFile(img.id, dest);
console.log('Downloaded:', dest);
}
EOF
- Read each downloaded image using the Read tool (Claude vision). For each note:
- Photography style: studio vs lifestyle vs clinical vs UGC
- Color temperature: warm/cool/neutral, any color grading
- Text overlay density: minimal / moderate / heavy
- Composition pattern: centered, split, person-forward, product-hero
- Mood and energy level
- Add "Existing Creative Style" section to Brand DNA with these observations — this informs the Prompt Modifier with real visual evidence, not assumptions.
- If no gallery folder or no images found: note it and proceed — Prompt Modifier will rely on screenshot + web research.
Step 1.3 — Web research
Use web search to fill any gaps not covered by CLIENT.md, screenshot, and gallery:
"[ClientName] [city/country] branding"— design agency credits, brand guidelines"[ClientName] Facebook ads"or Meta Ad Library — current ad creative approaches- 1–2 direct competitors for visual differentiation context
Skip searches where CLIENT.md + screenshot + gallery already provide sufficient detail.
Step 1.4 — Write and save Brand DNA
Compile all research into a structured document. Save locally AND upload to Drive via API.
# Brand DNA — [ClientName]
Generated: [DATE]
Drive cached: yes
## Brand Overview
- Business: [one sentence]
- Market: [Croatian / regional / other]
- Voice adjectives: [5 descriptors, e.g. "professional, warm, trustworthy, modern, local"]
- Positioning: [what makes them different]
## Visual System
- Primary font: [name or description, e.g. "geometric sans-serif, bold weight"]
- Secondary font: [body/supporting font]
- Primary color: [name + exact hex from screenshot]
- Secondary color: [name + exact hex]
- Accent color: [name + exact hex]
- Background colors: [white, off-white, dark, etc.]
- CTA color: [color + style]
## Logo
- Description: [exact visual description for Krea.ai: font style, text content, icon shape, colors]
- Example: "Bold condensed sans-serif 'ACME Agency' in sky blue (#your-channel), small gold lightning bolt icon to the left"
- Placement in ads: bottom right corner, white version on dark backgrounds
## Photography Direction
- Lighting: [e.g. "soft diffused natural light"]
- Color grading: [warm/cool/neutral, saturation]
- Composition: [e.g. "centered subjects, clean negative space"]
- Subject matter: [people, product, lifestyle, architectural, etc.]
- Props & surfaces: [typical backgrounds, objects in frame]
- Mood: [e.g. "approachable and professional"]
## Product / Service Details
- Core offering: [specific description]
- Visual appearance: [what it looks like in an ad]
- Distinctive elements: [logo marks, key visuals, brand identifiers]
## Existing Creative Style
[Observations from Galerija inspection — or "No gallery found" if empty]
- Photography: [observations]
- Copy density: [observations]
- Composition: [observations]
- Mood: [observations]
## Ad Creative Style
- Formats currently run: [what types they use]
- Text overlay: [minimal / heavy, style]
- Cultural cues: [Croatian market, language preference HR/EN]
## Image Generation Prompt Modifier
[50-75 word paragraph prepended to EVERY generated prompt. Must include: EXACT hex colors from screenshot, precise logo description, font style descriptors, photography direction, lighting, mood, cultural/market cue. End with logo placement instruction: "bottom right corner: [logo description]". This is the most critical output.]
Save locally: ACME Agency/clients/<ClientName>/brand-dna.md
Upload to Drive via API:
node --input-type=module <<'EOF'
import { uploadFile } from './ACME Agency/scripts/lib/google_drive.mjs';
const fileId = await uploadFile(
'ACME Agency/clients/<ClientName>/brand-dna.md',
'<drive_folder_id>',
'brand-dna.md'
);
console.log('Uploaded brand-dna.md, fileId:', fileId);
EOF
Tell the user: "Brand DNA saved locally and uploaded to Drive. Ready for Phase 2."
Phase 2 — Prompt Generation
Goal: Select the right templates, write or adapt copy, fill all placeholders → produce static_ads_prompts.json.
Step 2.0 — Detect input mode
Before doing anything else, determine which mode the user is in:
MODE A — Copy provided: User has given specific text (headlines, body copy, offers, testimonials).
- Select 5–8 templates that are the best match for the provided copy
- Use the user's copy verbatim where it fits; adapt it for templates that require a different format
- Example: if user provides a testimonial quote, prioritize templates 3, 6, 11, 17
MODE B — Brief or idea only: User gives a campaign concept, goal, product focus, or just the client name.
- Claude writes all copy from scratch using Brand DNA voice + audience insights
- Select 8–12 templates that make strategic sense for this client's industry (see guidance below)
- Justify each template selection in 1 line before showing the plan
In both modes: Before generating any prompts, show the user:
- Which templates were selected and why
- The copy you plan to use for text-heavy templates (testimonials, ACME Agencyws, headlines, stats)
- Ask for approval or adjustments before proceeding to prompt filling
Step 2.1 — Template selection by client type
Use these rules to guide template selection:
Service businesses (dental, medical, legal, real estate, financial):
- Lead with: 3 (Testimonials), 6 (Social Proof), 17 (Verified ACME Agencyw Card), 4 (Features/Benefits), 10 (Press/Editorial)
- Avoid product-centric templates (35, 22, 13) — adapt these for "service + person" if using them
E-commerce / product brands:
- Lead with: 1 (Headline), 2 (Offer/Promotion), 35 (Hero + Stat Bar), 11 (Pull-Quote ACME Agencyw), 25 (Us vs Them Color Split), 13 (Stat Surround Radial)
- UGC-style templates work very well: 8 (Before/After UGC), 29 (UGC + Viral Post Overlay), 32 (UGC Story Callout)
B2B / agency / professional services:
- Lead with: 10 (Press/Editorial), 23 (Manifesto/Letter Ad), 26 (Stat Callout), 5 (Bullet-Points), 7 (Us vs Them)
- Avoid heavy UGC/selfie formats — they don't match B2B authority positioning
Croatian market note: Lifestyle and testimonial formats convert better in the Croatian market. Heavy number/stat ads perform better in US/UK markets. When in doubt, lead with human faces and clear local context.
Step 2.2 — Fill templates
For each selected template from references/template-prompts.md:
- Keep the opening line "Use the attached images as brand reference." — Krea.ai supports
imageUrlsand the script passes up to 5 product images from the client folder automatically - Prepend the Image Generation Prompt Modifier from Brand DNA to the start of the prompt
- Replace ALL
[BRACKETED PLACEHOLDERS]with brand-specific details - Ensure text overlay copy is in the correct language (Croatian for Croatian-market clients unless instructed otherwise)
- Do not leave any
[BRACKETS]in the final prompt — every placeholder must be resolved
Copy writing guidance for text overlays:
- Headlines: punchy, benefit-driven, max 8 words
- CTAs: action verbs — "Zakažite", "Saznajte više", "Kontaktirajte nas", "Pogledajte ponudu"
- Testimonials and ACME Agencyws: use realistic, specific, credible language matching the industry
- Stats: use real numbers from CLIENT.md if available; otherwise plausible benchmarks marked
[VERIFY]
Step 2.3 — Save prompts JSON
Save to ACME Agency/clients/<ClientName>/static_ads_prompts.json:
{
"client": "ClientName",
"generated_at": "ISO timestamp",
"brand_dna_version": "YYYY-MM-DD",
"input_mode": "A | B",
"prompts": [
{
"template_number": 3,
"template_name": "testimonials",
"prompt": "Full completed prompt ready for Krea.ai Nano Banana 2...",
"aspect_ratio": "9:16",
"reference_tags": ["logo", "interior"],
"notes": "Copy uses fictional testimonial — replace with real patient quote before publishing"
}
]
}
reference_tags rules:
- Every prompt should have
reference_tagslisting which named assets fromclients.json > reference_assetsto pass as Krea reference images "logo"is auto-prepended at runtime bystatic_ads_generate.mjs— you do NOT need to include it inreference_tags. It's still fine if it's there (deduped automatically).- Add content-specific assets only where relevant: a testimonial ad benefits from an environment shot; a text-only manifesto does not
- If a template uses a real person as a visual reference (team member, professional, spokesperson), add this sentence to the prompt: "Maintain the exact appearance and likeness of the reference person — do not stylize, idealize, or alter their face, age, or physical characteristics."
- If
clients.jsonhas noreference_assetsyet, omitreference_tags— the script falls back toproduct-images/folder automatically
Show the user a summary (template name + first 80 chars of prompt) and ask for approval before Phase 3.
---
## Phase 3 — Image Generation
**Goal:** Run batch generation via Krea.ai and deliver organized output.
### Step 3.1 — Confirm count and cost
State before running:
- Number of images: `[templates selected] × [batch]`
- Estimated cost: ~48 Krea compute units per image at 2K
- Ask: "Ready to generate?" unless user has already confirmed
### Step 3.1b — Reference images
The script routes images per-template using `reference_tags` in the prompts JSON:
1. **Named assets** — if `reference_tags` is set, maps each tag to a URL from `clients.json > reference_assets`
2. **Fallback** — if no `reference_tags`, uploads images from `product-images/` (or client root) on-the-fly
Krea receives up to 5 reference images per generation call. It uses them for visual consistency (logo, brand colors, people, environments) — it places them naturally rather than being forced into a fixed position.
**Before running Phase 3:** confirm `clients.json > reference_assets` has at least `"logo"` populated. If not, run `logo_prepare.mjs` first (Step 1.1c).
### Step 3.1c — Drive output folder
Images are automatically uploaded to a structured folder inside the client's Drive:
<client drive_folder_id> └── Social media design/ └── <client.market or "Croatia">/ └── <year>/ └── <month>/
Folders are created on demand if missing. To set a non-default market, add `"market": "Germany"` (or whichever) to the client entry in `clients.json`.
### Step 3.2 — Run the script
node ACME Agency/scripts/static_ads_generate.mjs "ClientName" \ [--templates 1,3,7] \ [--batch 1] \ [--resolution 2K] \ [--no-slack] \ [--no-drive] \ [--no-images] # skip image reference, pure text-to-image
**Default settings:**
- Templates: all in `static_ads_prompts.json`
- Batch: 1 image per template
- Resolution: 2K
- Slack: enabled | Drive: enabled | Image reference: enabled (if images found)
### Step 3.3 — Output
The script saves images to `ACME Agency/clients/<ClientName>/static-ads/<NN-template-name>/`, uploads all to Drive, posts Slack report grouped by template, and appends to `static_ads_manifest.json`.
After completion: tell the user the Drive folder link and ask if they want to iterate on any template.
## Verification (run AFTER Phase 3 — prove it shipped, don't assume)
Rubric: [verify.md](verify.md). Two layers — one enforced, one you run.
**Deterministic (automatic, in `static_ads_generate.mjs`).** Every batch now runs
`validateFiles()` from `shared/verify/assert_output.mjs` BEFORE Drive upload: any 0-byte /
sub-10 KB Krea stub is dropped (never uploaded, never counted as a shipped ad), logged
`[Verify] … dropped`, and recorded as `invalid` in `static_ads_manifest.json`. The summary
line excludes them. So "size > 10 KB" and "no silent stub" are now proven in code, not
hoped. Still confirm from the run output: success count matches selected templates (failed
templates are listed, not hidden), Drive folder created, every valid image has a Drive URL,
Slack post returned a non-null `ts`.
**Vision quality (you run, before the client sees them).** Invoke the `verifier` agent on
a representative sample of the generated PNGs against [verify.md](verify.md) (threshold 8).
Regenerate any template it scores < 8 via `--templates N` (cap 2 passes); only ≥ 8 ships.
This is what catches garbled text, a wrong/redrawn logo, and off-brand color — the failures
the file-size check can't see. If the brand is consistently off, fix `brand-dna.md` and
re-run Phase 2 before regenerating.
Never report "all done" while any layer shows a gap — name the specific failure.
---
## Selective & Re-run Flows
| Goal | Command |
|------|---------|
| Re-run prompts only (new campaign, same brand) | `--phase 2` |
| Force new Brand DNA research | `--refresh` |
| Generate only specific templates | `--templates 3,11,17` |
| A/B testing — 4 variations of winners | `--templates 3,6 --batch 4` |
| Quick test run before full batch | `--templates 1,3,10 --batch 1` |
---
## Key Files
| File | Purpose |
|------|---------|
| `.claude/skills/references/template-prompts.md` | **Primary template library — 40 ad formats** |
| `.claude/skills/static-ad-generator/templates.md` | Quick 12 simplified templates (reference only) |
| `ACME Agency/scripts/static_ads_generate.mjs` | Batch generation script |
| `ACME Agency/scripts/lib/krea.mjs` | Krea.ai API wrapper |
| `ACME Agency/scripts/lib/slack.mjs` | Slack posting |
| `ACME Agency/scripts/lib/logo_prepare.mjs` | Screenshots logo from live website, uploads to Drive, returns public URL |
| `ACME Agency/clients/clients.json` | Client registry (drive_folder_id, slack_channel) |
| `ACME Agency/clients/<Name>/CLIENT.md` | Source brand context |
| `ACME Agency/clients/<Name>/brand-dna.md` | Brand DNA (local copy) |
| `ACME Agency/clients/<Name>/static_ads_prompts.json` | Filled prompts (Phase 2 output) |
| `ACME Agency/clients/<Name>/static-ads/` | Generated images (Phase 3 output) |
| `ACME Agency/clients/<Name>/static_ads_manifest.json` | Generation history |
---
## Tips for Better Output
- **Test before full batch.** Run `--templates 1,3,10` first to validate the Prompt Modifier. If the style is off, adjust Brand DNA and re-run Phase 2 before firing all 40.
- **Richer CLIENT.md = better Brand DNA = better ads.** Ask the user for the website URL and 2–3 "ads we like" examples before Phase 1 if CLIENT.md is sparse.
- **Stats and ACME Agencyws are fictional** — always note this to the user. They should substitute real testimonials and verified numbers before publishing.
- **Croatian copy:** Keep it natural and human, not corporate. Short active sentences. "Zakažite odmah" beats "Iskoristite mogućnost zakazivanja".
- **Mode B quality tip:** Ask the user one question before writing copy — "What's the single most important thing this campaign needs to communicate?" — then make every template answer that question differently.