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.

Ir para a instalação

Informações da origem

Repositório
criptogus/agent-evolve-network
Última atividade na origem
4 de julho de 2026 às 17:42
Idioma detectado do SKILL.md
inglês
Estrelas
289
Forks
2

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
2 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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.
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub