AgentStack
SKILL verified MIT Self-run

Uni1 Image Ad

skill-krusemediallc-uni1-image-ad-uni1-image-ad · by krusemediallc

Use when the user wants to generate a Meta/Facebook image ad with Luma uni-1 and attach it to an existing Meta ad set. Triggers on phrases like "uni-1 ad", "make a Luma ad creative", "new image ad in <ad set name>", "uni-1 image ad for Meta", "generate uni-1 ad creative", "upload uni-1 image as Meta ad". Anchors on uni-1 + Meta together — does NOT trigger on a generic "make an ad" without the uni…

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-krusemediallc-uni1-image-ad-uni1-image-ad

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Uni1 Image Ad? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

uni1-image-ad

Generate one or more uni-1 images and create paused Meta ads from them, attached to an existing ad set in the user's Meta ad account.

Hard rules — never relax

These come from the user's Luma brand contract and from "don't spend money by accident":

  1. Model is uni-1. Never substitute photon-1, photon-flash-1, or any other model. The brand contract requires every promotional image be generated by uni-1. The helper script enforces this; you must not work around it.
  2. Always clone from an existing ad. The user must supply an existing ad ID to clone — page, ad set, link URL, and CTA are read from that ad. Never hand-pick a page or ad set independently. If the user does not provide an ad ID, ask for one before any other input.
  3. Claude writes the new ad copy. Body and headline are generated fresh per variant, tailored to the new image. The cloned ad's copy is reference for tone/voice only — never copy it verbatim.
  4. Ad status is PAUSED. Never pass --status ACTIVE or otherwise unpause an ad. The user reviews and launches manually in Ads Manager.
  5. No campaign or ad-set mutations. New ads are attached to the cloned ad's existing ad set. The skill never creates campaigns or ad sets.
  6. Confirmation gate before every Meta CLI mutation. Show the full command, wait for explicit approval, then run.
  7. Audit log first, then mutate. Append the JSONL row when a creative is created, before attempting the ad. Orphaned creative IDs must be recoverable from ./generated/runs.jsonl.
  1. No screenshot/platform chrome in generated images. The output of generate_image.py must be the standalone ad creative — the static image the advertiser uploads. Never render iOS device chrome (status bar, home indicator), platform brand-row headers ("Sponsored"/"Saved"), post text/captions surrounding the ad, link-card footers ("URL.COM | headline | Learn more"), engagement rows (likes/comments/shares), or platform tab bars. The script auto-appends a no-chrome guard suffix to every prompt by default. If a use case genuinely requires screenshot-style chrome (rare — usually a UGC-aesthetic ad), pass --allow-chrome explicitly and explain why.

Generation modes

The helper script supports two modes; pick based on what the user asks for.

| Mode | When to use | Required | Optional | |---|---|---|---| | image (default) | Generate a brand-new ad image. | --prompt, --aspect-ratio | --image-ref (up to 8) for product/style grounding | | image_edit | Modify an existing image (swap colors, change background, add an element). | --prompt, --source | --image-ref (up to 8) for additional guidance |

Reference images are how you keep brand fidelity. When the user wants the ad to feature a specific product (jar, bottle, package), pass that product image via --image-ref. The model holds packaging, wordmarks, and label typography much more faithfully when it has the asset to ground on. Without a reference, output is "inspired by" — labels and wordmarks will be approximate.

Refs and source can be local file paths (PNG/JPG/JPEG/WEBP/GIF). The script base64-encodes them; no need to upload anywhere.

Inputs the user must provide (or you must elicit)

The user-facing surface is small — most ad fields come from the cloned ad. Cache last_clone_ad_id per account in ~/.claude/skills/uni1-image-ad/state.json.

| Input | Source | Notes | |---|---|---| | Existing ad ID to clone | User (required) | Ad must live in the configured AD_ACCOUNT_ID. Cache as default for next run. | | Seed prompt | User | The creative direction in their words. You will rewrite it (see Phase 3). | | Mode | User or inferred | image (default) or image_edit. Use image_edit only if the user is editing a specific source image. | | Source image (--source) | User | Required only for image_edit. | | Reference image(s) (--image-ref) | User | Optional but strongly recommended when the ad features a specific product. Up to 8. | | Variant count N | User, default 1 | Cap at 5. | | Aspect ratio | User | One of 1:1, 1.91:1, 4:5, 9:16. Reject anything else. Ignored in image_edit mode. | | Ad name (base) | User, optional | Defaults to · uni-1 vN. You suffix per variant. | | CTA override | User, optional | Defaults to the cloned ad's CTA. Override only if user explicitly asks. |

Derived from the cloned ad (do NOT ask the user — read these via meta ads ad get + meta ads creative get):

| Field | Comes from | |---|---| | Ad set ID | ad.adset_id of the cloned ad | | Page ID | creative.object_story_spec.page_id of the cloned creative | | Link URL | creative.object_story_spec.link_data.link | | CTA | creative.object_story_spec.link_data.call_to_action.type | | Cloned body / title | Reference for tone only — used in Phase 3.5 to write new copy |

Workflow

Phase 1: Preflight

Verify in this exact order; bail on the first failure with a fix-it message.

  1. The current working directory has a .env file containing LUMA_API_KEY, ACCESS_TOKEN, and AD_ACCOUNT_ID.
  2. Run meta auth status. It must print Authenticated. On non-zero, instruct the user to refresh their token (see references/meta-cli-flags.md § Token refresh quick steps).
  3. Read ~/.claude/skills/uni1-image-ad/state.json. If accounts[].last_clone_ad_id exists, offer it as the default for "ad ID to clone".

Phase 2: Gather inputs and clone the source ad

  1. Ask for the ad ID to clone if not already supplied. Show cached last_clone_ad_id as the default. Refuse to proceed without one.
  1. Resolve the ad via meta -o json ads ad get . From the response, capture:
  • adset_id — the destination for the new ad
  • creative.id — to fetch the cloned creative
  1. Resolve the creative via meta -o json ads creative get . From the response, capture:
  • object_story_spec.page_id (or object_story_spec.link_data.page_id) → page_id
  • object_story_spec.link_data.linklink_url
  • object_story_spec.link_data.call_to_action.typeCTA
  • object_story_spec.link_data.message → cloned body (tone reference)
  • object_story_spec.link_data.name → cloned title (tone reference)
  1. Show the cloned values to the user as a compact summary so they can sanity-check. Example:

`` Cloning from ad 120246xxxxxxxxxxxx ("Old Headline · v3") Ad set: 120243594343360512 (Luma AI Testimonial Ads) Page: 1234567890 (Mr. Paid Social) Link: https://lumalabs.ai/api CTA: LEARN_MORE Body (cloned, for tone reference): "…" ``

  1. Then gather the new-image inputs: seed prompt, mode (default image), --source if image_edit, --image-ref paths, variant count N, aspect ratio, optional ad-name base, optional CTA override.
  1. Validate aspect ratio strictly. Reject anything not in {"1:1", "1.91:1", "4:5", "9:16"}.

Phase 3: Prompt rewrite

This is where you add value. First check the prompt library, then either fill a template or write fresh.

3a — Check the prompt library

Load references/prompt-library.md. It has 7 validated parameterizable templates:

| Tag | Format | When | |---|---|---| | T1 | Apple Notes listicle | "Why I switched to X" / sentimental list of reasons | | T2 | Editorial article hero | Publication-co-signed credibility ad (FORBES, WIRED, Vogue, etc.) | | T3 | Story tweet+UGC composite | Authority quote + UGC photo for Stories/Reels | | T4 | Fake Google search mosaic | "Best X for Y" with publication logos as social proof | | T5 | Comparison table (light) | Brand vs. category competitor with feature checklist | | T6 | Comparison table (dark, hooky) | Stop-the-scroll dark-mode "this RUINS X" hook | | T7 | Sticky-note + product flatlay | Tactile UGC-style, 30-day-test reviewer voice |

If the user's seed prompt or brief maps onto one of these, use it: read the matching section in prompt-library.md, fill in the {placeholder} variables for the user's brand, and use that as the rewritten prompt. Tell the user which template you matched and why.

If nothing fits, write fresh — then propose adding the new pattern to the library after generation succeeds.

3b — Fleshing out a fresh prompt (or filling a template)

When writing or completing a prompt, anchor on:

  • Subject and pose
  • Lighting and time of day
  • Lens / framing
  • Color palette / mood (pull from the brand's identity)
  • Composition (rule of thirds, leading lines)
  • Negative space for text overlay if the ad has body/headline copy
  • Reference roles — if --image-ref is being used, name each reference's role explicitly in the prompt (e.g. "the product in imageref[0]", "the lighting from imageref[1]"). Luma's docs note that multi-reference quality improves when each reference is labeled in the prompt.
  • Standalone-creative scope — never describe iOS chrome, Sponsored badges, engagement counts, or platform UI. The script's no-chrome guard catches violations, but write the prompt as if the rule is on you.

Show the rewritten prompt to the user as one block. Tell them which template (if any) it's based on. Ask: "Use this, edit it, or start over?" Loop until approved.

Phase 4: Generate

Run the helper script. Always pass --env-file pointing at the project .env. Add --image-ref once per reference (max 8). Switch to --mode image_edit --source if the user is editing rather than creating from scratch.

# image mode (default), with optional brand reference
~/.claude/skills/uni1-image-ad/scripts/generate_image.py \
  --prompt "" \
  --aspect-ratio  \
  --n  \
  --image-ref  \
  [--image-ref ] \
  --out ./generated \
  --env-file .env

# image_edit mode (modify an existing image)
~/.claude/skills/uni1-image-ad/scripts/generate_image.py \
  --prompt "" \
  --mode image_edit \
  --source  \
  [--image-ref ] \
  --n  \
  --out ./generated \
  --env-file .env

Reference-grounded generations typically take ~40–60s (vs ~6s text-only). Don't bail early — the script's poll timeout is 240s.

Each line on stdout is JSON for one variant (variant, path, generation_id, width, height). Display the paths to the user.

If any variant comes back below 1080×1080, regenerate it (the script logs a warning to stderr but still emits the JSON line — you decide whether to keep it). If all variants failed, stop and report the error from stderr.

Phase 5: Confirm variants

Show all paths and ask: "Use all / use these specific ones / regenerate / cancel." One confirmation covers all selected variants.

Phase 5.5: Write new ad copy

Two sub-steps: first read the data, then write copy informed by it. Don't skip the data read — generic copy is the #1 failure mode of this skill.

5.5a — Pull what's actually winning spend in this account
~/.claude/skills/uni1-image-ad/scripts/top_spending_ads.py \
  --limit 10 --days 30

That prints one JSON-per-line for the top-10 spending ads in the configured account, with body, title, spend, impressions, ctr, and creative_id. Read all of it, then derive a short pattern profile:

  • Voice — first-person? founder? testimonial? brand-distant?
  • Length distribution — what's the median body length of the top performers?
  • Bullet style, , , no bullets, emoji?
  • Hook structures — counter-narrative, statistic, personal anecdote, threat?
  • Credibility anchors — recurring numbers, names, claims?
  • CTA closersJoin us →, See the demo, Comment X, no closer?

State the pattern profile to the user as a compact table before drafting copy. This is the diff between "I made up some copy" and "I matched what's working."

5.5b — Write copy that matches the pattern profile

For each selected variant, you (Claude) write a new body and headline. Anchor on:

  • The new image — what it actually shows. Lead with the visual hook.
  • The pattern profile from 5.5a — match length distribution, voice, bullet style, CTA closer. The data is the spec.
  • The cloned ad's voice — secondary signal; never copy phrasing verbatim.
  • A copy framework from references/ad-copy-frameworks.md — load that file before writing. Pick a framework that fits the image + the dominant pattern in 5.5a.
  • For multi-variant runs, use different frameworks per variant so the user A/B tests structure, not just wording.

No hard character cap. Meta truncates around the read-more fold, but if the top spenders run 400-700 char bodies and that's clearly working in this account, write to that length. The data decides, not a rule of thumb. (Exception: titles still fit under ~40 chars to avoid placement-level truncation.)

Show all variants' copy as a numbered table for review, including which framework each used and a one-line note on which top-spending ad it pattern-matches:

v1  [framework: First Person]  matches: 120245165245740512 ("Refresh. Duplicate. Pause. Pray.")
  title:  …
  body:   …
v2  [framework: Statistic]     matches: 120243595999050512 ("The math doesn't lie.")
  title:  …
  body:   …

Ask: "Use this copy / edit specific lines / regenerate." Loop until approved before any Meta CLI calls.

Phase 6: Upload loop

For each selected variant i:

  1. Compose the creative-create command using the cloned page_id, link_url, and CTA (or override) and the new copy from Phase 5.5. Use references/meta-cli-flags.md for the exact flag spelling. Quote all string values with embedded spaces.

``bash meta -o json ads creative create \ --name " v" \ --image \ --page-id \ --body "" \ --title "" \ --link-url "" \ --call-to-action ``

  1. Show the command, ask "run this?". Run on yes. Capture the creative ID.
  1. Append to ./generated/runs.jsonl immediately (one JSON per line):

``json {"ts": "", "prompt": "", "variant": , "generation_id": "", "image_path": "", "creative_id": "", "creative_id_status": "created", "ad_id": null, "ad_id_status": "pending"} ``

  1. Compose the ad-create command using the cloned adset_id:

``bash meta -o json ads ad create \ --name " v" \ --creative-id ``

  1. Show, confirm, run. Capture the ad ID. Update the JSONL row in place (read all lines, replace this one, rewrite) with ad_id and ad_id_status: "created".
  1. If ad-create fails: leave ad_id_status: "failed" with an error field. Tell the user the creative was created (give the ID) and offer to retry just the ad-create step.

Phase 7: Persist cache, report

  1. Update ~/.claude/skills/uni1-image-ad/state.json:

``json { "accounts": { "": { "last_clone_ad_id": "", "last_run_ts": "" } } } ``

  1. For each created ad, print:
  • The ad ID
  • Deep link: https://business.facebook.com/adsmanager/manage/ads?act=&selected_ad_ids= (strip the act_ prefix from AD_ACCOUNT_ID)
  1. Remind the user: all ads are PAUSED — review and launch manually.

Retry mode (partial-failure recovery)

If runs.jsonl already exists when the user re-runs the skill, scan it for rows where creative_id_status == "created" but ad_id_status != "created". Offer:

> "I see orphan creative(s) from a previous run. Do you want to retry the ad create step against those existing creatives, or generate a new image?"

If retry: skip Phases 3–5, jump to Phase 6 step 4 with the existing creative IDs.

Dry-run mode

If the user says "dry run", "preview only", or "show me the commands", run Phases 1–4, then in Phase 6 just print every meta command without executing, and exit. Do not write to runs.jsonl in dry-run mode.

Out of scope — fail clearly

If the user asks for any of these, stop and explain it's not supported:

  • Creating ads without cloning from an existing one. The skill always clones. If the user ha

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.