Multi-agent toolkit for Google Ads. Read, analyze, and gate-managed mutate paths across Search, Performance Max, App, Display, Shopping, and Video. Conversion-tracking and Google tag audits. Keyword research via Keyword Plan. Auction Insights. Placement safety with built-in exclusions for scams, bots, politics, religion, games, gambling, and adult content. Campaign creation behind explicit context gates.
End-user Google sign-in via the gcloud CLI. No service account, no per-user OAuth client to register. Hard 24-hour session cap on top of token expiry.
Three layers:
- Python adapters under
scripts/that call the Google Ads API and return JSON. - Subagent definitions under
agents/, one per analysis domain. - Skills under
skills/that route/gads <command>to the right agent.
The audit orchestrator (skills/gads-audit/) gates on auth, runs
conversions + Google tag checks first, then fans out the rest in
parallel and renders a markdown report.
- Python 3.10 or newer
- Google Cloud SDK (
gcloud) - A Google Ads developer token registered against a manager account (one-time setup at https://ads.google.com/aw/apicenter)
python -m venv .venv
# macOS / Linux
source .venv/bin/activate
# Windows PowerShell
.venv\Scripts\Activate.ps1
pip install -r scripts/requirements.txt
End-user sign-in through gcloud:
python scripts/gads_auth.py --adc
That prints the exact gcloud auth application-default login command
to run. Run it. A browser opens, sign in, grant scope. Then register a
profile for each manager account you work with:
python scripts/gads_auth.py --add-profile acme --developer-token <TOKEN> --login-customer-id <MCC-id>
python scripts/gads_auth.py --add-profile widgets --developer-token <TOKEN2> --login-customer-id <MCC2>
Switch the active profile any time:
python scripts/gads_auth.py --use-profile widgets
python scripts/gads_auth.py --list-profiles
A single-MCC setup just adds one profile and stays on it. The old flat credentials file (one token, no profiles) is migrated to a "default" profile automatically the first time the script runs.
Verify:
python scripts/gads_auth.py --check
python scripts/gads_auth.py --customers
The session is good for 24 hours. After that the scripts refuse to run until you sign in again. This is enforced locally regardless of token TTL.
The non-API logic is covered by pytest. No credentials needed.
cd scripts
python -m pytest -q
132 tests covering: auth profile lifecycle, session expiry, GAQL builder shapes, placement classification, campaign-context validation, report rendering (markdown + HTML), the session-gate hook, search-term mining, anomaly detection, audit history persistence and diff, recommendation triage, pretty-print fallback and table renderer, bid strategy recommendation rules, budget pacing thresholds, ad-asset audits (RSA strength + PMax coverage), Quality Score component weakness ranking, demographic/location outlier rules, creative brief parsing, prompt scaffold shape, attach field-type validation, Telegram formatter and credential storage, and the PostToolUse notification hook.
In Claude Code, slash commands map to skills:
/gads audit <customer-id>
/gads search <customer-id>
/gads pmax <customer-id>
/gads uac <customer-id>
/gads display <customer-id>
/gads shopping <customer-id>
/gads youtube <customer-id>
/gads conversions <customer-id>
/gads gtag <customer-id> --site <url>
/gads keywords <customer-id> --seeds w1 w2 ...
/gads competitors <customer-id>
/gads placements <customer-id>
/gads recommendations <customer-id> # Google's own actionable list
/gads anomalies <customer-id> # day-level metric anomalies
/gads bidstrategy <customer-id> # per-campaign bid strategy fit
/gads pacing <customer-id> # MTD vs budget projection
/gads assets <customer-id> rsa # responsive search ad strength
/gads assets <customer-id> pmax-assets # PMax asset coverage
/gads brands <customer-id> suggest --query "Acme"
/gads geos <customer-id> --query "California"
/gads creative <customer-id> <site-url> # site -> brief -> prompts (BYO images) -> upload + attach
/gads quality <customer-id> # per-keyword QS + weakest component
/gads demographics <customer-id> all # age + gender + device + location
/gads notify --setup --token T --chat-id C # Telegram notifications
/gads history <customer-id> --changes # change_event log
/gads history <customer-id> --diff a b # compare two saved audits
/gads create <customer-id>
/gads audit --all-customers # multi-account fan-out
Scripts pretty-print a compact summary by default. Pass --json for
machine output.
Outside Claude Code, the same things run as plain Python:
python scripts/gads_search.py --customer <id> --days 28 --negative-candidates --json
python scripts/gads_placements.py --customer <id> --days 28 --json
python scripts/gads_recommendations.py --customer <id> --json
python scripts/gads_anomalies.py --customer <id> --days 30 --baseline-days 14 --z 2.0 --json
python scripts/gads_history.py --customer <id> --changes --days 7 --json
python scripts/gads_audit.py --customer <id> --days 28 --site https://example.com --save-history --output audit.json
python scripts/gads_history.py --customer <id> --list --json
python scripts/gads_history.py --customer <id> --diff <ts-a> <ts-b> --json
python scripts/gads_report.py --input audit.json --format md --output audit.md
python scripts/gads_report.py --input audit.json --format html --output audit.html
python scripts/gads_creation.py --customer <id> --context-file ctx.json --validate-only --json
python scripts/gads_creation.py --customer <id> --context-file ctx.json --apply --json
python scripts/gads_apply.py --customer <id> negatives --input negs.json --validate-only --json
python scripts/gads_apply.py --customer <id> placements --input excl.json --apply --json
Two optional hooks ship with the project:
hooks/session_gate.py— PreToolUse. Blocksgads_*Bash calls once the 24h local session is expired and prints the gcloud command to re-authenticate.hooks/notify_telegram.py— PostToolUse. Sends critical audit findings to Telegram. Silent until configured.
See docs/HOOKS.md for the settings.json wiring, the event flow, the four gates the Telegram hook applies, multi-account behaviour, and one-time setup.
gads_placements.py enumerates every placement that served impressions
in the window and classifies each against
scripts/placements_rules.json. Default categories: scam, bot,
politics, religion, games, gambling, adult, mfa. The agent
shows the proposed exclusion list per category and waits for y/N
before writing the negative criteria.
The rules file is plain JSON. Edit it, point at a custom file via
--rules, or override per-run.
gads-creation refuses to propose a mutate until you've supplied:
- business / vertical
- website (reachability is checked)
- primary goal (sales, leads, traffic, awareness, app installs)
- analytics installed (verified via
gads-gtag) - conversion actions correct (verified via
gads-conversions) - daily budget
- bidding strategy
- geo and language
- channel type
The script returns either blocked (with a list of missing or
contradictory fields) or ready (with a proposed mutate JSON). Nothing
is sent until the user approves the JSON. New campaigns are created
PAUSED.
google-ads-agents/
agents/ subagent definitions, one per domain
scripts/ Python adapters and tests
skills/ /gads command routing
hooks/ placeholder for pre/post-tool guards
docs/ setup guide
MIT. See LICENSE.