PORTAL / LIBRARY / google-ads-optimize

[ PAID MEDIA ]

/google-ads-optimize

Full Google Ads search campaign optimization for a ACME Agency client.

Download the skill file (.md)

Placeholders like ACME Agency, <id> and you@example.com mark values that are per-agency — your install fills them with YOUR clients and accounts. If a section references a helper script you don't have yet, it ships with that workflow's install.

Triggers

What this skill does

  1. Identifies the client from the argument or ClickUp context
  2. Loads or builds CLIENT.md with business knowledge (Drive questionnaire + website)
  3. Determines analysis period (30 days, or 14 days if any campaign is < 30 days old)
  4. Pulls search terms, keywords, device, geo, and bidding strategy data
  5. Auto-applies campaign-level BROAD match negative keywords
  6. Reports only (no auto-apply) on bidding strategy, device, and geo opportunities
  7. Posts a full structured report to the client's Slack channel

How to run

# Full execution (applies negatives, posts to Slack)
node ACME Agency/scripts/google_ads_optimize.mjs --client "ACME Agency"

# Dry run — analysis only, no writes, no Slack post
node ACME Agency/scripts/google_ads_optimize.mjs --client "ACME Agency" --dry-run

Interactive step-by-step (when running manually, not via ClickUp executor)

Step 1 — Identify client

Step 2 — Load or build CLIENT.md

Check ACME Agency/clients/<ClientFolder>/CLIENT.md.

If it exists: read it — it has business context, brand terms, competitors, and "never negate" list.

If it doesn't exist, fetch from:

  1. Google Drive: gws drive files list --folder-id <drive_folder_id> → find file with "upitnik" in name → gws drive files get --file-id <id>
  2. Client website: fetch landing_page URL from clients.json
  3. Synthesize and save as ACME Agency/clients/<ClientFolder>/CLIENT.md
Note: gws CLI must be run via bash shell on Windows, not via Node execFileSync. If drive_folder_id is missing in clients.json, skip Drive step and log a warning.

CLIENT.md format:

# CLIENT.md — <ClientName>
## Business type
## Products/services
## Market/geography
## Target audience
## Competitors
## Keywords to never negate
## Brand terms

Step 3 — Determine analysis period

Pull all SEARCH campaigns and check campaign.start_date.

Step 4 — Pull data in parallel

Step 5 — Analyze search terms

Uses analyzeSearchTerms() from lib/google_ads.mjs plus client context:

Auto-negate (apply automatically):

Competitor campaign rule:

Protect via CLIENT.md:

Suggest for ACME Agencyw (don't auto-apply):

Step 6 — Apply negatives

Step 7 — Bidding strategy analysis (report only)

For each SEARCH campaign:

Step 8 — Device analysis (report only, 100+ clicks threshold)

Aggregate all SEARCH campaign data by device across the period:

Step 9 — Geo analysis (report only, 100+ clicks threshold)

Aggregate geographic data:

Step 10 — Save raw output to JSON

Before delegating, save the full data payload to:

ACME Agency/clients/<ClientFolder>/<id>.json

Contains: campaign summary, raw search terms, applied negatives, suggested negatives, device data, geo data, bidding diagnostics. The analyst reads from this file in the next step.

Step 11 — Delegate analysis to the analyst agent

Invoke the analyst subagent via the Task tool with subagent_type: "analyst". Pass:

client: <client name>
data_source: <absolute path to <id>.json from Step 10>
data_type: google-ads
period: last 30 days (or whatever window was analyzed)
compare_to: previous 30 days (if comparison data is in the file)
focus: search-term waste + bidding/device/geo opportunities
output_format: sections

The analyst returns summary (Slack-ready), details (for thread), and <id>. Do NOT analyze the JSON yourself in the main context.

Step 12 — Deliver via slack-reporter

Invoke the slack-reporter subagent via the Task tool with subagent_type: "slack-reporter". Pass:

client: <client name>
channel: <channel ID resolved via getClientChannel(clientName)>
type: google-ads-optimize
headline: "*Google Ads — <ClientName> — <YYYY-MM-DD>*"
sections: <derived from analyst's summary block>
extra_sections:
  - title: "Negatives applied"
    bullets: <list from Step 6>
  - title: "Suggested for ACME Agencyw"
    bullets: <list from Step 6>
links:
  - label: "Detalji"
    url: <Drive doc URL if generated>
thread_details: <analyst's details + device/geo breakdowns>
language: <client's language from clients.json>

Do NOT call <id>() or any helper from lib/slack.mjs directly. The slack-reporter agent owns Slack delivery.

Verification (run AFTER Step 12 — prove it shipped, don't assume)

Rubric: verify.md. The mutation check is now enforced in google_ads_optimize.mjs: after applying negatives it re-fetches each campaign's live negative list (getExistingNegatives) and confirms every intended term is present — a 200 from the write is not proof. Watch the run for [optimize] Verify: N/M confirmed live; any miss is logged per-campaign with the API errors (result.errors, previously ignored) and lands in reportData.negativesVerification. A miss usually means a policy-exemptible keyword (e.g. <id>) was dropped — do NOT report those as applied; resubmit with <id> (see createSearchKeywords).

Then check the rest before declaring done:

If any check fails, name the gap explicitly. Never claim success when verification fails — for Google Ads especially, wrong negatives can take down a real account.

Key files

FilePurpose
ACME Agency/scripts/google_ads_optimize.mjsMain optimization script (standalone + exported function)
ACME Agency/scripts/lib/google_ads.mjsGoogle Ads API library: <id>, getSearchTerms, getDeviceBreakdown, getGeoBreakdown, analyzeSearchTerms, addNegativeKeywords
ACME Agency/scripts/lib/slack.mjsSlack reporting: <id>
ACME Agency/scripts/clickup_executor.mjsClickUp task runner — delegates google_ads_negatives + google_ads_optimize to this skill
ACME Agency/clients/clients.jsonClient registry: google_ads_id, drive_folder_id, landing_page, folder
ACME Agency/clients/<ClientFolder>/CLIENT.mdPer-client business knowledge (auto-created if missing)

Important rules

Example ClickUp tasks that trigger this skill