Skip to main content

upload-ad-sample

Upload a finished Meta or video ad creative (image or video) to the Goose Ads sample library and fill its remix payload — recipe, script, render variant, and (for video formats like iMessage) the source conversation thread — so the sample is remixable in the app. Use after a creative is finalized and approved for showcasing on the public ads pages.

Zur Installation springen

Quellinformationen

Repository
criptogus/agent-evolve-network
Letzte Quellaktivität
4. Juli 2026 um 17:42
Erkannte Sprache von SKILL.md
Englisch
Sterne
289
Forks
2

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
upload-ad-sample
description
Upload a finished Meta or video ad creative (image or video) to the Goose Ads sample library and fill its remix payload — recipe, script, render variant, and (for video formats like iMessage) the source conversation thread — so the sample is remixable in the app. Use after a creative is finalized and approved for showcasing on the public ads pages.
tags
["ads","marketing","internal"]
# Upload Ad Sample Publish a finished ad creative (video or image) into the Goose Ads sample library, and — when the upload comes from a real ad build — fill the **remix payload** so the sample is remixable in the app, not just a static showcase. This is the canonical way to add new samples; the admin panel uses the same endpoints under the hood. **This is an agent-executed skill.** No external script. The agent does the upload and registration inline using curl (or any HTTP client available in its environment). **Access:** the write endpoints require an API token whose owner is on the admin allowlist (or an admin session). A regular user/API token is rejected with 403. If you get a 403, the token isn't admin-allowlisted — tell the user. ## When to use - A creative video or image has just finished and is approved for public showcasing on the marketing library page. - You want a sample to also feature on the homepage ads grid — set `is_featured: true`. - **You want the sample to be remixable.** Two remix shapes exist: - **Video formats (iMessage, etc.)** — pass `format_key` + `thread_json` so the format tab can show the sample AND the remix recipe can run it. See [Filling the video remix payload](#filling-the-video-remix-payload-imessage-and-other-video-formats). - **Meta UGC ads from a content-goose project** — pass the project's production JSON so worlds/characters/voices are parsed into `remix_spec`. See [Filling the remix payload from a content-goose project](#filling-the-remix-payload-from-a-content-goose-project). ## Inputs | Field | Required | Default | Notes | |---|---|---|---| | `title` | yes | — | Short headline, e.g. "Friend-asks-friend SPF reveal". | | `format` | yes | — | `video` or `image`. | | `ratio` | yes | — | `9:16`, `1:1`, `4:5`, or `16:9`. | | `file_path` | yes (or `media_url`) | — | Local path to the video/image file. | | `media_url` | yes (or `file_path`) | — | Public https URL of an already-hosted file. | | `thumbnail_url` | no | — | Poster image URL. **Required for video** — auto-generated from a poster frame if absent (see below). | | `brand` | no | — | Brand name shown on the card, e.g. "Clinikally". | | `tags` | no | `[]` | Free-form tags array, e.g. `["imessage", "ugc"]`. | | `is_published` | no | `false` | When `true` the sample is publicly visible immediately. Otherwise it's a draft, only visible in the admin panel. | | `is_featured` | no | `false` | When `true` the sample also appears on the homepage ads grid (subject to ordering). | | `is_remixable` | no | auto | When `true` the sample appears in the app's **create / remix picker** — INDEPENDENT of `is_published` (which gates the landing-page showcase). Defaults to `true` when a `recipe` is provided, else `false`. Set it explicitly to keep the remixable set separate from the showcase set (`is_published:true, is_remixable:false` = showcase-only; `is_published:false, is_remixable:true` = remixable but not on the landing page). | | `sort_order` | no | `0` | Higher numbers appear earlier inside the featured grid. | | `slug` | no | auto | Readable handle (e.g. `clinikally-imessage-spf`). The server auto-generates one from brand + title if omitted. Lowercase, hyphenated. | | **`format_key`** | no | — | **Ad-format key for video formats**, e.g. `imessage`. Set this when the sample should be remixable through a format tab. Lowercase, hyphenated. | | **`thread_json`** | no | — | **Path to the build's conversation thread JSON** (the file the renderer used, e.g. the build's `threads/full-thread.json`). Required to build a video `recipe`. | | **`render_variant`** | no | `iphone-frame` (imessage) | **How the source was rendered:** `iphone-frame` (phone bezel) or `plain` (full-screen chat). The remix keeps the source's variant, so record it truthfully. | | **`duration_sec`** | no | ffprobe | **Master video length in seconds.** Stored in `metadata`. If omitted, derive it with ffprobe on the master. | | `recipe` | no | — | Built automatically from `format_key` + `thread_json` for video; a shot-list JSON object for other formats. | | `extracted_script` | no | — | The source ad's script (plain text). Built automatically from the thread for video. | | `skills_used` | no | `[]` | Flat atom slug list. **Atom slugs only — never a molecule** (see note below). Superseded by `remix_spec.skills` for UGC ads — prefer `remix_spec`. | | `how_to` | no | — | Agent-facing production notes (the project's how-to). | | `remix_spec` | no | — | Structured `{ version, skills[], worlds[], characters[], voices[] }` for Meta UGC ads. Build it with the parse endpoint below. | > **`skills` and `skills_used` are ATOM slugs, never a molecule.** Record the > atomic capability skills the ad actually used (`generate-voiceover`, > `generate-broll-shot`, `compose-master`, …) — not the parent molecule that > orchestrated them. A molecule slug is ambiguous on the remix side: it implies > "run every sub-skill inside it," which over-specifies the recipe. The parse > endpoint already returns atoms; if you set `skills_used` by hand, keep it > atom-level too. If both `file_path` and `media_url` are provided, prefer `file_path` (we re-host the file under our own bucket). **Fill the remix payload whenever the sample comes from a real ad build** — for video formats that means `format_key`, `recipe`, and `extracted_script`; for Meta UGC ads it means `recipe`, `extracted_script`, `skills_used`, `how_to`, and `remix_spec`. Omit them only for a bare showcase upload that won't be remixed. ## Environment The skill resolves the API base from the first of: 1. `$GOOSEWORKS_API_BASE_URL` 2. `$GOOSEWORKS_API_URL` 3. Default: `https://app.gooseworks.ai` Auth uses `$GOOSEWORKS_API_TOKEN` as a Bearer token (it must belong to an admin-allowlisted user — see Access above). If neither env var is set, ask the user where the API lives before proceeding. ## How to run The flow is **3 HTTP calls** when uploading a local file, or **1 call** when the asset is already hosted. For video, add a small thumbnail upload (below). ### Flow A — local file #### 1. Request a presigned PUT URL ```bash curl -sS -X POST "$API_BASE/api/ads-library/samples/upload-url" \ -H "Authorization: Bearer $GOOSEWORKS_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "filename": "<basename of file_path>", "content_type": "<derived MIME>", "kind": "media" }' ``` Derive the MIME from the file extension: | Extension | content_type | |---|---| | `.mp4` | `video/mp4` | | `.mov` | `video/quicktime` | | `.webm` | `video/webm` | | `.png` | `image/png` | | `.jpg`, `.jpeg` | `image/jpeg` | | `.webp` | `image/webp` | | `.gif` | `image/gif` | Response: ```json { "status": "success", "data": { "upload_url": "https://<bucket>.s3.<region>.amazonaws.com/...", "public_url": "https://<bucket>.s3.<region>.amazonaws.com/ads/samples/<uuid>/media-<filename>", "key": "ads/samples/<uuid>/media-<filename>", "expires_in_seconds": 900, "required_headers": { "content-type": "<MIME>", "cache-control": "public, max-age=31536000, immutable" } } } ``` The presigned URL expires in 15 minutes — do step 2 promptly. #### 2. PUT the file bytes to S3 **Send every header in `required_headers` from step 1 exactly.** The presigner signs both `content-type` AND `cache-control` into the signature, so a PUT that omits the `Cache-Control` header is rejected by S3 with `SignatureDoesNotMatch` (403). Mirror the response's `required_headers`: ```bash curl -sS -X PUT "<upload_url>" \ -H "Content-Type: <MIME>" \ -H "Cache-Control: public, max-age=31536000, immutable" \ --data-binary "@<file_path>" ``` A `200` response means the file is uploaded. #### 3. Register the sample ```bash curl -sS -X POST "$API_BASE/api/ads-library/samples" \ -H "Authorization: Bearer $GOOSEWORKS_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "<title>", "format": "<format>", "ratio": "<ratio>", "media_url": "<public_url from step 1>", "thumbnail_url": "<required for video — see Thumbnails>", "brand": "<optional>", "tags": ["..."], "is_published": false, "is_featured": false, "slug": "<optional readable handle>", "format_key": "<optional, e.g. imessage>", "recipe": <optional recipe object — see the remix sections>, "extracted_script": "<optional script>", "metadata": <optional object, e.g. { "duration_sec": 24 }>, "skills_used": ["..."], "how_to": "<optional production notes>", "remix_spec": <optional UGC remix_spec from the parse endpoint> }' ``` A `201` response with `data.id` confirms the sample was registered. The remix fields are all optional and nullable — include whatever you have. Assemble the **full** body and POST it **once** (see the one-POST rule below); don't create a bare sample and PATCH the remix fields in afterwards. ### Flow B — already-hosted media Skip steps 1 and 2. Call step 3 directly with `media_url` set to the existing public URL. (Video still needs a `thumbnail_url`.) ### Fallback if step 1/2 hit CORS or the bucket isn't public-PUT-enabled There's a server-side proxy upload that streams through the API: ```bash curl -sS -X POST "$API_BASE/api/ads-library/samples/upload" \ -H "Authorization: Bearer $GOOSEWORKS_API_TOKEN" \ -F "file=@<file_path>" \ -F "kind=media" ``` Returns `{ data: { public_url, key, content_type, size_bytes } }`. Use the `public_url` in step 3. Prefer the presigned flow when it works — this proxy limits files to 250 MB and pushes bytes through Express memory. ### Thumbnails (required for video) Every **video** sample needs a `thumbnail_url` — the library and format-tab cards render a poster, and a video with no thumbnail shows a blank tile. If the caller didn't supply one, grab a poster frame from the master and upload it as a thumbnail: ```bash # 1. Extract a poster frame (1s in) from the master ffmpeg -y -ss 1 -i "<file_path>" -frames:v 1 -q:v 2 "<tmp>/poster.jpg" # 2. Presign with kind: "thumbnail" (thumbnails must be an image MIME) curl -sS -X POST "$API_BASE/api/ads-library/samples/upload-url" \ -H "Authorization: Bearer $GOOSEWORKS_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "filename": "poster.jpg", "content_type": "image/jpeg", "kind": "thumbnail" }' # 3. PUT the poster with BOTH required headers (content-type + cache-control) curl -sS -X PUT "<thumbnail upload_url>" \ -H "Content-Type: image/jpeg" \ -H "Cache-Control: public, max-age=31536000, immutable" \ --data-binary "@<tmp>/poster.jpg" ``` Use the thumbnail's `public_url` as `thumbnail_url` in the step-3 register call. Images don't need this — only video. ## Filling the video remix payload (iMessage and other video formats) When the sample is a video built for a format tab (iMessage today), pass `format_key` and `thread_json` so the app can both **show** it under the format tab and **remix** it. The remix recipe reads `recipe.thread`, `recipe.render_variant`, and `extracted_script` to rebuild the conversation for a new brand, so build them exactly as below. ### 1. Build the `recipe` Read and parse the thread JSON file at `thread_json` (the conversation the renderer used — typically the build's `threads/full-thread.json`). Then build: ```jsonc { "format": "<format_key>", // e.g. "imessage" "render_variant": "<iphone-frame|plain>", // how this source was rendered; default "iphone-frame" for imessage "thread": <the parsed thread JSON, verbatim> } ``` `recipe.thread` is stored **verbatim** — keep whatever the build emitted. The iMessage thread schema is the conversation the renderer drives: a top-level object with `participants[]` (each `{ id, name, self? }`) and an ordered `messages[]` array of `{ id, type: "text"|"typing"|"attachment", from, text?, src?, … }`. Some older builds use a flatter `{ peer_persona, bubbles[], end_card }` shape — either is fine; store what the build produced. Don't hand-rewrite or trim it. `recipe` must be a JSON **object** and serialize to ≤ 64 KB. A normal iMessage thread is a couple KB; if a thread is unusually large (many inline data-URL attachments), host the attachments and reference them by URL rather than inlining the bytes.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen