Skip to main content

schedule-ads

Manage paid ads on AdManage.ai from declarative config - default schedules launches across Meta/TikTok/Snapchat/Pinterest/LinkedIn (always PAUSED); create provisions Meta campaigns and ad sets.

跳到安装

来源信息

仓库
aeonfun/aeon
最近来源活动
2026年8月4日 21:01
检测到的 SKILL.md 语言
英语
星标
738
分支
265

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
3 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
schedule-ads
description
Manage paid ads on AdManage.ai from declarative config - default schedules launches across Meta/TikTok/Snapchat/Pinterest/LinkedIn (always PAUSED); create provisions Meta campaigns and ad sets.
metadata
{"title":"Schedule Ads","category":"productivity","var":"Selects which flow runs (parse from ${var}):\n- empty / unset (default) → SCHEDULE branch: read config.yaml, pick schedule\n entries matching today, and launch those ads in-run via AdManage.ai.\n Launches PAUSED by default; dailySpendCap circuit-breaker; never auto-activates\n live spend.\n- \"create\" → CREATE branch: read config.create.yaml, diff against\n .admanage-state/campaigns.json, and create the missing Meta campaigns + ad sets\n in-run. On-demand; creates entities PAUSED; returned IDs are written back into\n state so the schedule branch can launch into them.\n","schedule":"0 8 * * *","commits":true,"permissions":["contents:write"],"tags":["growth","ads"],"requires":["ADMANAGE_API_KEY"]}
> **${var}** selects the flow. Empty/unset = **schedule** (launch ads into existing ad sets). `create` = **create-campaign** (provision Meta campaigns + ad sets). Both are config-driven, PAUSED-by-default, and make the AdManage API calls **in-run** via `./secretcurl` (the `{ADMANAGE_API_KEY}` placeholder keeps the key off the command line), behind fail-closed spend guardrails. Reads a declarative config, computes what to do, and makes the AdManage.ai API calls **in-run** via `./secretcurl`. The calls are an irreversible outbound side-effect (real ad spend), so they are each branch's **final** actions and run only behind the guardrails below (PAUSED-by-default, `dailySpendCap` circuit-breaker, dry-run). `ADMANAGE_API_KEY` is injected in-run via this skill's `requires:` — always write it as the `{ADMANAGE_API_KEY}` placeholder, never a bare `$ADMANAGE_API_KEY` (the Bash permission layer refuses that). ## Preamble (both branches) 1. Read `memory/MEMORY.md` for context. Read the last ~3 days of `memory/logs/` for recent launch / provisioning activity — don't re-report a signal already logged. 2. Parse `${var}`: - empty / unset → run the **Schedule branch** below. - `create` → run the **Create branch** below. - anything else → log `SCHEDULE_ADS_UNKNOWN_SELECTOR: <value>` and exit cleanly (no notify). 3. Both branches spend real money on ad platforms. The shared safety posture (see each branch) is: PAUSED by default, config-only (never invent campaigns/creative/targeting), dry-run available, and exit silently when there's nothing to do. --- # Schedule branch (default — empty `${var}`) Reads `skills/schedule-ads/config.yaml`, picks schedule entries matching today, and launches those ads **in-run** via AdManage.ai (`POST /v1/launch` through `./secretcurl`), behind the spend guardrails below. ## Safety defaults (schedule) This branch **spends real money on ad platforms**. Guardrails, in priority order: 1. **PAUSED by default.** Every launch request sets the entity to PAUSED. The operator has to resume manually in the AdManage dashboard before spend starts. `launchPaused: false` in config is the explicit opt-out. 2. **Daily spend cap.** Before launching, the branch checks `GET /v1/spend/daily` for today. If spend ≥ `dailySpendCap` in the config, all launches are skipped and a warning is notified. If the spend figure can't be verified (malformed / empty response), **fail closed** — skip and notify, don't launch. This is a circuit breaker, not a budget enforcer — platform budgets still apply. 3. **Dry-run mode.** If `DRY_RUN=true` in env or `dryRun: true` in config, the branch builds the payloads, writes them to `.pending-admanage/dryrun/`, notifies what *would* launch, and exits without calling the API. 4. **Config-only.** The branch does not invent campaigns, creative, or targeting. If there's no schedule for today, it exits cleanly with no API calls. 5. **Single source of truth.** All ads/campaigns/targeting live in `config.yaml`. The branch never generates new creative on the fly. ## Network note (schedule) Launching ads is an irreversible outbound side-effect (real ad spend), so it is the branch's **final** action and runs only after the guardrails above pass: - Auth'd calls go through `./secretcurl` with the `{ADMANAGE_API_KEY}` placeholder — never a bare `$ADMANAGE_API_KEY` (the Bash permission layer refuses that). `ADMANAGE_API_KEY` is injected in-run via `requires:`. - The branch checks the daily spend cap (`GET /v1/spend/daily`), then per batch calls `POST /v1/launch`, polls `GET /v1/batch-status/{id}` to a terminal state, and reports via `./notify`. - If `ADMANAGE_API_KEY` is unset, or the launch/spend call fails, skip the launch and notify — do not retry blindly. There is no deferred postprocess fallback. ## Steps (schedule) 1. **Load config.** Read `skills/schedule-ads/config.yaml`. If the file doesn't exist, log `SCHEDULE_ADS_NOT_CONFIGURED` and exit cleanly (no notify, no error). The example template lives next to this file as `config.example.yaml`. 2. **Validate config shape.** Required top-level keys: `defaults` (with `adAccountId`, `workspaceId`, `page`), and `schedules` (array). If either is missing, file an issue in `memory/issues/` per the CLAUDE.md issue tracker convention, notify once, and exit. 3. **Pick today's schedule entries.** For each entry in `schedules`, match against today's date: - `when.everyDay: true` → always matches. - `when.dayOfWeek: monday` (or any weekday name, lowercase) → matches if today is that weekday (UTC). - `when.date: "2026-04-25"` → matches only on that exact date. - `when.dates: ["2026-04-25", "2026-05-02"]` → matches if today is in the list. - `when.cron: "0 8 * * 1"` → (advanced) matches if today satisfies the cron. Optional — skip if it's too much parsing effort. If no entries match today, log `SCHEDULE_ADS_NOTHING_TODAY` and exit cleanly (no notify). 4. **Build launch payloads.** For each matching schedule entry, construct the AdManage `POST /v1/launch` body: ```json { "ads": [ { "adName": "<templated from ad.adName, {date} replaced>", "adAccountId": "<from defaults or entry override>", "workspaceId": "<from defaults or entry override>", "title": "<from ad>", "description": "<from ad>", "cta": "<from ad or defaults.cta>", "link": "<from ad>", "page": "<from defaults>", "insta": "<from defaults, Meta only>", "adSets": [ { "value": "<id>", "label": "<name>" } ], "media": [ { "url": "<media url>" } ], "status": "PAUSED" } ] } ``` Enforce `status: PAUSED` on every ad unless `defaults.launchPaused` is explicitly `false`. Never strip it silently. Template substitutions inside string fields: - `{date}` → today's ISO date (YYYY-MM-DD) - `{dateHuman}` → "April 21, 2026" style 5. **Pre-flight validation.** For each payload: - `media[*].url` must be an absolute `https://` URL. Reject entries with local paths or obviously broken URLs. - `adSets[*].value` must be a non-empty string. If missing, skip the entry with a warning in the log. - For Meta entries (`adAccountId` starts with `act_`): `page` and `insta` must be set. TikTok/Snapchat/etc. have their own requirements — don't block on Meta-specific fields for other platforms. - `title` and `description` must be non-empty. Drop invalid entries, keep going. Log which ones were skipped and why. 6. **Handle dry-run.** If `DRY_RUN=true` or `config.dryRun: true`: - Write payloads to `.pending-admanage/dryrun/{schedule-name}-{timestamp}.json`. - Notify a preview (see step 9) but with `[DRY RUN]` prefix. - Skip step 7. - This mode exists for the operator to sanity-check before arming real launches. 7. **Launch in-run.** This is the branch's final action — spends real money, so run the guardrails first. Only `./secretcurl`, `jq`, `date`, `echo`, `mkdir`, `grep`, `python3`, and the `Write` tool are available. a. **Config check.** `[ -n "${ADMANAGE_API_KEY:+x}" ]` (the `${VAR:+x}` form — a bare `$ADMANAGE_API_KEY` trips the secret-expansion analyzer and reads as unset). If unset → notify "ads computed but ADMANAGE_API_KEY missing — nothing launched" and stop. b. **Daily spend circuit-breaker (once).** Take the strictest `dailySpendCap` (`CAP`) across today's payloads. If set, read today's spend and **fail closed** unless it's a clean number *below* the cap: ```bash SPEND=$(./secretcurl -sS --max-time 30 -H "Authorization: Bearer {ADMANAGE_API_KEY}" \ "https://api.admanage.ai/v1/spend/daily?startDate=$TODAY&endDate=$TODAY" | jq -r '.metadata.totalSpend // ""') echo "$SPEND" | grep -qE '^[0-9]+(\.[0-9]+)?$' || { echo "spend unverifiable — fail closed"; exit 0; } echo "$CAP" | grep -qE '^[0-9]+(\.[0-9]+)?$' || { echo "dailySpendCap not numeric — fail closed"; exit 0; } python3 -c "import sys; sys.exit(0 if float(sys.argv[1])>=float(sys.argv[2]) else 1)" "$SPEND" "$CAP" \ && { echo "daily spend cap tripped (today=$SPEND cap=$CAP) — launching nothing"; exit 0; } ``` c. **Per batch: launch, then poll.** For each payload `{ ads: [ ... ] }`: ```bash RESP=$(./secretcurl -sS --max-time 60 -w 'http=%{http_code}\n' -X POST "https://api.admanage.ai/v1/launch" \ -H "Authorization: Bearer {ADMANAGE_API_KEY}" -H "Content-Type: application/json" -d "$PAYLOAD") # success => .success==true and .adBatchId set; else record FAILED (.message/.error) and continue. # Poll GET /v1/batch-status/$BATCH_ID (~90s, 5s interval) until .summaryStatus is success|error. ``` Record each batch's outcome (ok / error / still-running-after-timeout) for the notify. Ads launch **PAUSED** (the payload sets it) unless `launchPaused: false`. 8. **Write artifact to `output/.chains/schedule-ads.md`** so downstream chain consumers can read what was queued. Format: ```markdown # Schedule Ads — ${today} Queued: N launches across M schedules. Dry-run: yes|no. ## Entries - <schedule name>: <ad count> ads, platform=<meta|tiktok|…>, paused=<bool> - <adName> — <title> ``` 9. **Notify** via `./notify`. Keep it tight: ``` *Ads queued — ${today}${dryRunSuffix}* <N> launches queued from <M> schedules. - <schedule name> → <ad count> ads <platform> <paused|LIVE> "<first adName>" - ... <if dry-run> no API calls made — remove DRY_RUN to arm. <else> launched via AdManage (PAUSED) — resume in the dashboard to start delivery. ``` If nothing matched today (no launches), don't notify at all. 10. **Log** — see the shared **Log** section below (discriminator: `schedule`). ## Config schema (schedule) See `skills/schedule-ads/config.example.yaml` for a filled-in template. Minimum viable config: ```yaml defaults: adAccountId: act_XXXXXXXXXX workspaceId: XXXXXXXXXXXX page: XXXXXXXXXXXX # Meta Page ID insta: XXXXXXXXXXXX # Instagram user ID cta: LEARN_MORE launchPaused: true # NEVER change this without thought dailySpendCap: 50 # USD. Circuit breaker. dryRun: false schedules: - name: weekly-promo platform: meta when: { dayOfWeek: monday } adSets: - { value: "120xxxxxxxxxxxxx", label: "US Broad 25-55" } ads: - adName: "Weekly promo — {date}" title: "Headline copy here" description: "Supporting copy in a sentence or two." cta: LEARN_MORE link: https://example.com media: - url: https://media.admanage.ai/your-account/hero.mp4 ``` ## What the schedule branch does NOT do - **Does not create campaigns or ad sets.** Those must pre-exist in AdManage — use the **`create` branch** (`${var}=create`), the dashboard, or `POST /v1/manage/create-campaign` separately. This branch only launches *ads into existing ad sets*. - **Does not upload creative.** Media URLs must be hosted somewhere accessible (AdManage CDN, your own CDN, Supabase, wherever). If you need upload, add a separate `upload-ad-media` skill that calls `POST /v1/media/upload/url`. - **Does not generate copy.** Titles/descriptions come from config. If the operator wants AI-written variants, a separate skill can write them into `config.yaml` and commit — keeps the launch path boring and auditable. - **Does not manage budgets, bids, or targeting.** Everything downstream of launch (scaling, pausing losers, budget shifts) lives in follow-up skills or the dashboard. - **Does not launch to Google Ads, Axon, or Taboola** in v1. Config schema is deliberately Meta/TikTok/Snapchat/Pinterest/LinkedIn-shaped. Adding Google/Axon later is straightforward but their launch shapes differ enough to need their own validation. --- # Create branch (`${var}=create`) Reads `skills/schedule-ads/config.create.yaml`, figures out which campaigns/ad sets don't exist yet, and creates them **in-run** via AdManage.ai (`/v1/manage/create-*` through `./secretcurl`) — campaigns first, then ad sets referencing the returned campaign IDs — writing the new IDs back to `.admanage-state/campaigns.json`. This branch is **on-demand** — invoke it manually when you want to provision new campaigns, then reference the returned IDs in `skills/schedule-ads/config.yaml` (schedule branch) to launch creatives into them. Read `.admanage-state/campaigns.json` (if it exists) to see what's already created. ## What this branch provisions Two entity types only: 1. **Meta campaigns** — name, objective, budget, bid strategy, promoted object. 2. **Meta ad sets** — name, budget, optimization goal, targeting (geo/age/platforms), destination. Everything else (TikTok/Snapchat/Pinterest/LinkedIn campaigns, advanced Meta fields like valueRuleSetId or Advantage+ catalog) is v2+. The shape below is intentionally minimal. ## Safety defaults (create) Same posture as the schedule branch: 1. **PAUSED by default.** Every campaign + ad set is created with `status: PAUSED`. No surprise spend. 2. **Idempotent.** The branch tracks created entities in `.admanage-state/campaigns.json`. If a campaign name already exists in state, it's skipped. Run it twice → no duplicates. 3. **Dry-run mode.** `DRY_RUN=true` or `config.dryRun: true` → payloads written to `.pending-admanage/dryrun-create/`, notified, no API calls. 4. **Config-only.** No config file → exit silently. No invented campaigns, no autonomous provisioning. ## Network note (create) Provisioning campaigns and ad sets is an irreversible outbound side-effect, so it is the branch's **final** action and runs in-run only after the diff + validation pass: - Auth'd calls go through `./secretcurl` with the `{ADMANAGE_API_KEY}` placeholder — never a bare `$ADMANAGE_API_KEY`. The key is injected in-run via `requires:`.
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看