Agent skill

Schedule Ads

by aeonfun in aeonfun/aeon

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.

MITAuto-check passedMarketing & SEO

Install Schedule Ads

skills CLI
$ npx skills add aeonfun/aeon --skill schedule-ads -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install aeonfun/aeon schedule-ads --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/schedule-ads .claude/skills/schedule-ads && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
schedule-ads
GitHub stars
767
Token cost
~6.5k tokens
SKILL.md length
2,543 words
Files
3
Skills in repo
82
Repo updated
First seen
Licence
MIT

At a glance

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.

  • Works in 3 steps: Read memory/MEMORY.md for context. Read… → Parse ${var} → Both branches spend real money on ad…
  • Tasks that involve Paid advertising
  • SKILL.md covers Preamble (both branches), Safety defaults (schedule), Network note (schedule) and Steps (schedule), plus 12 more sections
  • Reaches media.admanage.ai; needs ADMANAGE_API_KEY

What it does

Schedule Ads is an agent skill from aeonfun/aeon. 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.

Its SKILL.md is about 6.5k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `config.create.example.yaml` and `config.example.yaml`).

It sits in Marketing & SEO, covering Paid advertising. It works with LinkedIn, Pinterest and TikTok. The repository describes itself as: The most autonomous AI agent framework: runs unattended on GitHub Actions, self-healing skills, drives Claude Code, Grok, Codex & more. No approval loops. Configure once, forget… The licence is MIT.

When your agent uses it

  • Tasks that involve Paid advertising

Example prompts

  • “/schedule-ads”

Requirements

  • Python 3
  • A credential in ADMANAGE_API_KEY

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  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…
  2. Parse ${var}
  3. Both branches spend real money on ad platforms. The shared safety posture (see each branch) is: PAUSED by default, config-only (never…

What it can do on your machine

Read from SKILL.md and the folder at commit c0cb7c4. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are json, yaml and markdown).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • media.admanage.ai

    Also links to:

    • api.admanage.ai

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • ADMANAGE_API_KEY

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Schedule Ads loads about 6.5k tokens when it runs. Until then it costs about 52 tokens; SKILL.md has 2,543 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~52
When it runs · the whole SKILL.md, loaded when a task matches
~6.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from aeonfun/aeon at commit c0cb7c4, republished under its MIT licence (© aeonfun). 2,543 words, ~6,452 tokens.

Download SKILL.mdSave it as .claude/skills/schedule-ads/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.
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
metadata.category
productivity
metadata.var
Selects which flow runs (parse from ${var}): - empty / unset (default) → SCHEDULE branch: read config.yaml, pick schedule entries matching today, and launch…
metadata.commits
true
metadata.permissions
contents:write
metadata.tags
growth, ads
metadata.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.
Show full SKILL.md (1,152 more words)Show less

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:.
  • Order matters: create all campaigns first (POST /v1/manage/create-campaign), keep a config-name → campaignId map, then create ad sets (POST /v1/manage/create-adset) substituting each parent's real campaign ID. Write every new ID back to .admanage-state/campaigns.json as you go (the workflow's Commit step persists it).
  • If ADMANAGE_API_KEY is unset, or a create call fails, record the failure and continue with the rest — never retry blindly, never invent IDs. An ad set whose parent campaign failed to create is skipped. There is no deferred postprocess fallback.

Steps (create)

  1. Load config. Read skills/schedule-ads/config.create.yaml. If it doesn't exist, log CREATE_CAMPAIGN_NOT_CONFIGURED and exit cleanly (no notify). The example template lives next to this file as config.create.example.yaml.

  2. Load state. Read .admanage-state/campaigns.json. If it doesn't exist, treat as empty. Shape:

    json
    {
      "campaigns": [
        {
          "configName": "Prospecting — Q2 2026",
          "campaignId": "120251616228380456",
          "adAccountId": "act_xxx",
          "createdAt": "2026-04-21T08:00:00Z",
          "adSets": [
            {
              "configName": "US Broad 25-54",
              "adSetId": "120251616242460456",
              "createdAt": "2026-04-21T08:00:04Z"
            }
          ]
        }
      ]
    }
  3. Validate config shape. Required: defaults.adAccountId, defaults.workspaceId, campaigns[]. Each campaign needs name and objective. Each ad set needs name, and either optimizationGoal (explicit) or a compatible parent objective. If validation fails, file an issue in memory/issues/ and exit.

  4. Compute diff. For each campaign in config:

    • Match against state by exact name. If present, mark as existing.
    • If missing, mark as new and queue a campaign create.
    • For each ad set under the campaign, match against the parent's adSets[] in state by name. If missing, mark it for creation (carrying a parentCampaignConfigName reference you resolve to a real campaign ID in-run, once the parent campaign create returns).

    If nothing is new, log CREATE_CAMPAIGN_ALL_EXIST and exit without notify.

  5. Build campaign create payloads. Per the AdManage POST /v1/manage/create-campaign shape:

    json
    {
      "businessId": "<adAccountId>",
      "workspaceId": "<workspaceId>",
      "name": "<campaign.name>",
      "objective": "<campaign.objective>",
      "status": "PAUSED",
      "buyingType": "AUCTION",
      "specialAdCategories": [],
      "dailyBudget": <number>,
      "bidStrategy": "<LOWEST_COST_WITHOUT_CAP | LOWEST_COST_WITH_BID_CAP | COST_CAP | ...>",
      "promotedObject": { ... }
    }

    Skip keys that are null/absent in config — don't send empty strings. Always force status: PAUSED unless defaults.launchPaused: false is set explicitly.

  6. Build ad-set create payloads. Per POST /v1/manage/create-adset:

    json
    {
      "businessId": "<adAccountId>",
      "workspaceId": "<workspaceId>",
      "campaignId": "__RESOLVE_FROM_PARENT__",
      "parentCampaignConfigName": "<campaign.name>",
      "name": "<adSet.name>",
      "status": "PAUSED",
      "dailyBudget": <number>,
      "billingEvent": "IMPRESSIONS",
      "optimizationGoal": "<LANDING_PAGE_VIEWS | OFFSITE_CONVERSIONS | ...>",
      "destinationType": "<WEBSITE | PHONE_CALL | MESSAGING_... | ...>",
      "targeting": { ... },
      "promotedObject": { ... }
    }

    The __RESOLVE_FROM_PARENT__ sentinel + parentCampaignConfigName marks an ad set whose campaignId you fill in-run, from the map built as each campaign create returns (step 9b). If the parent campaign was existing (already in state), write the real campaign ID directly and drop the sentinel.

  7. Pre-flight validation.

    • adAccountId must start with act_ (this branch is Meta-only in v1).
    • dailyBudget must be a positive number in dollars (not cents).
    • objective must be one of the documented Meta objectives: OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_AWARENESS, OUTCOME_SALES, OUTCOME_APP_PROMOTION.
    • Targeting geo_locations.countries must be a non-empty array. Drop invalid entries, keep going, log what was skipped and why.
  8. Handle dry-run. If DRY_RUN=true or config.dryRun: true: write payloads to .pending-admanage/dryrun-create/ instead, notify with a [DRY RUN] prefix, skip step 9.

  9. Create in-run. This is the branch's final action — provisions real entities, so run only after the diff + pre-flight pass. Only ./secretcurl, jq, date, echo, python3, and the Write tool are available (no mv). Seed .admanage-state/campaigns.json to {"campaigns":[]} if missing.

    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 "campaigns computed but ADMANAGE_API_KEY missing — nothing created" and stop (state unchanged).

    b. Campaigns first. For each new campaign, POST /v1/manage/create-campaign:

    bash
    RESP=$(./secretcurl -sS --max-time 60 -w 'http=%{http_code}\n' -X POST \
      "https://api.admanage.ai/v1/manage/create-campaign" \
      -H "Authorization: Bearer {ADMANAGE_API_KEY}" -H "Content-Type: application/json" -d "$PAYLOAD")
    # success => .success==true and .campaignId set.

    On success: remember configName → campaignId (for step 9c) and append {configName, campaignId, adAccountId, createdAt, adSets:[]} to .admanage-state/campaigns.json. On failure: record the error, skip this campaign's ad sets.

    c. Then ad sets. For each new ad set, resolve campaignId: if it's __RESOLVE_FROM_PARENT__, look it up by parentCampaignConfigName in the map from 9b or existing state — if the parent isn't found (its create failed), skip the ad set with a warning. Then POST /v1/manage/create-adset (same ./secretcurl shape). On success: append {configName, adSetId, createdAt} under the parent campaign in .admanage-state/campaigns.json (via python3/Write — no mv).

    Ordering is explicit here (campaigns loop fully before the ad-sets loop), so children always reference a resolved parent ID.

  10. Write artifact to output/.chains/create-campaign.md so chain consumers can see what was queued:

    markdown
    # Create Campaign — ${today}
    
    New campaigns: N.
    New ad sets: M.
    Dry-run: yes|no.
    
    ## Campaigns
    - <name> — <objective>, $<dailyBudget>/day
      - ad set: <name> — <optimizationGoal>, $<dailyBudget>/day, <countries>
    
    ## Skipped (already exist)
    - <name>
  11. Notify via ./notify. Tight format:

    *Campaigns queued — ${today}${dryRunSuffix}*
    
    <N> campaigns, <M> ad sets queued for creation.
    
    - <campaign name>
      - adset: <adset name> — <country>, $<budget>/day
    
    <if dry-run>
    no API calls made — remove DRY_RUN to arm.
    <else>
    created via AdManage (PAUSED); new IDs written to .admanage-state/campaigns.json.

    If nothing is new, don't notify at all.

  12. Log — see the shared Log section below (discriminator: create).

Config schema (create)

See skills/schedule-ads/config.create.example.yaml for a filled-in template. Minimum viable config:

yaml
defaults:
  adAccountId: act_XXXXXXXXXX
  workspaceId: XXXXXXXXXXXX
  launchPaused: true               # never flip without a reason
  dryRun: false                    # true = build, don't call

campaigns:
  - name: "Prospecting — Q2 2026"
    objective: OUTCOME_TRAFFIC
    dailyBudget: 50
    bidStrategy: LOWEST_COST_WITHOUT_CAP
    promotedObject:
      pixel_id: "123456789012345"
    adSets:
      - name: "US Broad 25-54"
        dailyBudget: 15
        optimizationGoal: LANDING_PAGE_VIEWS
        destinationType: WEBSITE
        targeting:
          geo_locations: { countries: ["US"] }
          age_min: 25
          age_max: 54
          publisher_platforms: [facebook, instagram]

Interaction with the schedule branch

The create branch writes new IDs to .admanage-state/campaigns.json within the same run; from there they're yours to reference in skills/schedule-ads/config.yaml (schedule branch) under adSets[].value. The two flows are intentionally decoupled:

  • create branch provisions structure (container).
  • schedule branch launches creative into that structure (contents).

They still don't auto-chain — the schedule branch reads config.yaml, which you edit by hand. Pattern is: run ${var}=create (provisions + writes IDs in-run) → read the new IDs from .admanage-state/campaigns.json / the create-run notify → copy them into config.yaml → next default (schedule) run launches into them.

What the create branch does NOT do

  • Doesn't touch existing campaigns. Once a campaign is in state, this branch leaves it alone. Budget changes, bid changes, status flips, renames — all handled elsewhere (dashboard or a separate skill).
  • Doesn't delete or archive. No destructive paths.
  • Doesn't provision media, pages, or pixels. Pixel IDs must already exist in AdManage. Use GET /v1/conversions/pixels to discover them.
  • Doesn't create TikTok / Snapchat / Pinterest / LinkedIn structures. Those have different payload shapes and live in v2.
  • Doesn't resume paused campaigns. PAUSED is the end state; the operator unpauses manually when ready.

Log (both branches)

Append to memory/logs/${today}.md under ONE ### schedule-ads heading. First bullet is a discriminator naming which branch ran.

Schedule branch:

### schedule-ads
- Branch: schedule
- Schedules matching today: <names>
- Launches: <count> (dry-run: <bool>)
- Batch results: <ok/error/timeout summary> (live) | dry-run preview in .pending-admanage/dryrun/

Create branch:

### schedule-ads
- Branch: create
- New campaigns created: <count> (ok/fail)
- New ad sets created: <count> (ok/fail)
- State: new IDs written to .admanage-state/campaigns.json (live) | dry-run preview in .pending-admanage/dryrun-create/

Environment Variables

  • ADMANAGE_API_KEY — the AdManage.ai API key, injected in-run via this skill's requires: and used by both branches for the /v1/* calls. Always pass it as the {ADMANAGE_API_KEY} placeholder to ./secretcurl, never a bare $ADMANAGE_API_KEY on the command line.
  • DRY_RUN — optional. If true, forces dry-run mode regardless of config, in whichever branch runs.
  • Notification channels configured via repo secrets (see CLAUDE.md).

Output

End with a ## Summary block naming the branch that ran:

  • schedule: schedules matched today, payload count, dry-run yes/no, files written.
  • create: new campaigns queued, new ad sets queued, skipped (already-exist) count, dry-run yes/no, files written.

© aeonfun, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 2 other files in skills/schedule-ads of aeonfun/aeon.

  • SKILL.md
  • config.create.example.yaml
  • config.example.yaml

Open the folder on GitHubat commit c0cb7c4

Compare with similar skills

Schedule Ads next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Schedule Ads compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Schedule Ads this skillaeonfun/aeon767—~6.5kAutomated safety check: PassMIT
Paid Ads AuditAgriciDaniel/claude-ads9.8k—~1.5kAutomated safety check: PassMIT
Ad Creativeindranilbanerjee/digital-marketing-pro8551 repos~1.7kAutomated safety check: PassMIT
DesignOhh-889/skyroc7959 repos~3.1kAutomated safety check: PassMIT
Ad Creativecoreyhaines31/marketingskills54k—~6.2kAutomated safety check: PassMIT
Money Adsiamzifei/show-me-the-money1k—~2.4kAutomated safety check: PassCustom licence

Similar skills

  • Paid Ads Audit

    AgriciDaniel/claude-ads

    Runs a source-grounded paid advertising audit across up to 12 ad platforms, with parallel platform workers, deterministic scoring and a versioned JSON bundle.

    9.8k GitHub stars~1.5k tokensUpdated yesterday
    Marketing & SEOAuto-check passed
  • Ad Creative

    indranilbanerjee/digital-marketing-pro

    Generate 3-5 ad copy variations per platform — headlines, descriptions, and CTAs formatted to Google, Meta, LinkedIn, TikTok, X, and Pinterest specs — each scored 1-10 with policy-compliance flags…

    855 GitHub starsUsed in 1 repo~1.7k tokens
    Marketing & SEOAuto-check passed
  • Design

    Ohh-889/skyroc

    Comprehensive design skill: brand identity, design tokens, UI styling, logo generation (55 styles, Gemini AI), corporate identity program (50 deliverables, CIP mockups), HTML presentations…

    795 GitHub starsUsed in 9 repos~3.1k tokens
    Frontend & DesignAuto-check passed
  • Ad Creative

    coreyhaines31/marketingskills

    When the user wants to generate, iterate, or scale ad creative — headlines, descriptions, primary text, or full ad variations — for any paid advertising platform.

    54k GitHub stars~6.2k tokensUpdated today
    Marketing & SEOAuto-check passed
  • Money Ads

    iamzifei/show-me-the-money

    Paid advertising automation for Google Ads, Meta Ads, and other ad platforms.

    1k GitHub stars~2.4k tokensUpdated 1 mo ago
    Marketing & SEOAuto-check passed
  • Mena Ads

    growthack88/growth-marketing-os

    MENA Ads Command Center — a complete paid-ads operating system for the Arab world (Egypt, KSA, UAE, GCC, Levant, North Africa) and global accounts.

    115 GitHub stars~2.5k tokensUpdated 6 days ago
    Marketing & SEOAuto-check passed

More from aeonfun/aeon

All 82 skills in this repo
  • Browses open tasks on the TaskMarket agent-worker market and, with explicit operator approval, creates tasks, tracks submissions and submits finished work.

    767 GitHub stars~1.4k tokensUpdated today
    Auto-check passed
  • Sets up and manages an Aeon agent instance that runs skills on a schedule through GitHub Actions: starting, rescheduling, debugging, editing skills and mining chat history.

    767 GitHub stars~9k tokensUpdated today
    Auto-check: warnings
  • Reads a Base Account's address, portfolio and transaction history through the Base MCP server, and stays strictly read-only in unattended Aeon runs, reporting only changes.

    767 GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Audits every page of a site each day from its sitemap, scores on-page and technical SEO, checks duplicates across pages and reports what changed since the last run.

    767 GitHub stars~5.1k tokensUpdated today
    Auto-check passed
  • Action Converter

    aeonfun/aeon

    5 concrete real-life actions, leverage-scored against open loops with specificity and anti-fluff gates

    767 GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Aeon Config Doctor

    aeonfun/aeon

    Static linter for an Aeon instance's configuration that catches silent failures such as unquoted schedules, duplicate keys, unconfigured skills and broken MCP references.

    767 GitHub stars~3.3k tokensUpdated today
    Auto-check passed

Categories

Questions about Schedule Ads

What does Schedule Ads do?

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. Schedule Ads is an agent skill from aeonfun/aeon.ai from declarative config - default schedules launches across Meta/TikTok/Snapchat/Pinterest/LinkedIn (always PAUSED); create provisions Meta campaigns and ad sets.

When should I use Schedule Ads?

Schedule Ads fits situations like: tasks that involve Paid advertising.

How do I install Schedule Ads in Claude Code?

Run `npx skills add aeonfun/aeon --skill schedule-ads -a claude-code`. Or copy the skill folder (skills/schedule-ads in aeonfun/aeon) into .claude/skills/schedule-ads in your project. Claude Code loads it when a task matches its description.

How do I install Schedule Ads in Codex?

Run `npx skills add aeonfun/aeon --skill schedule-ads -a codex`. Or copy the skill folder (skills/schedule-ads in aeonfun/aeon) into .agents/skills/schedule-ads in your project. Codex loads it when a task matches its description.

Can I use Schedule Ads in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add aeonfun/aeon --skill schedule-ads -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/schedule-ads, .gemini/skills/schedule-ads, .github/skills/schedule-ads and .opencode/skills/schedule-ads in your project.

What does Schedule Ads need to run?

Going by SKILL.md and its folder, Schedule Ads needs credentials named ADMANAGE_API_KEY. Our summary lists: Python 3; A credential in ADMANAGE_API_KEY.

Does Schedule Ads access the network?

SKILL.md names 2 domains. In commands or code: media.admanage.ai; the agent is likely to contact it when it follows the instructions. As links in the text: api.admanage.ai. This is read from the text; nothing was executed.

Is Schedule Ads safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Schedule Ads use?

Schedule Ads is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Schedule Ads use?

About 6.5k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Schedule Ads?

Skills that share tags, products or a category with Schedule Ads: Paid Ads Audit (AgriciDaniel/claude-ads, 9.8k stars), Ad Creative (indranilbanerjee/digital-marketing-pro, 855 stars), Design (Ohh-889/skyroc, 795 stars) and Ad Creative (coreyhaines31/marketingskills, 54k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Schedule Ads?

aeonfun (a GitHub organization) maintains it in aeonfun/aeon, which has 767 GitHub stars. The repository holds 82 skills in this directory. The repository was last updated on October 8, 2026.

Source: aeonfun/aeon on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.