adloop 0.19.0__tar.gz → 0.19.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {adloop-0.19.0 → adloop-0.19.2}/PKG-INFO +5 -5
- {adloop-0.19.0 → adloop-0.19.2}/README.md +4 -4
- {adloop-0.19.0 → adloop-0.19.2}/pyproject.toml +1 -1
- {adloop-0.19.0 → adloop-0.19.2}/pyproject.toml.orig +1 -1
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/custom_conversion_goals.py +0 -2
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/read.py +6 -27
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/write.py +21 -2
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/server.py +37 -23
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/__main__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/ai_max.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/brands.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/client.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/conversion_actions.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/currency.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/enums.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/forecast.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/gaql.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/image_fetch.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ads/validate_only.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/auth.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/cli.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/config.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/crossref.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/diagnostics.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ga4/client.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ga4/reports.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ga4/tracking.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/ga4/write.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/gsc/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/gsc/client.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/gsc/reports.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/gtm/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/gtm/client.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/gtm/read.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/merchant/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/merchant/client.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/merchant/read.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/pagespeed.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/reddit/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/reddit/auth.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/reddit/client.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/reddit/read.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/reddit/schedule.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/reddit/write.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/adloop.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/commands/analyze-performance.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules/commands/optimize-campaign.md +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/rules_install.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/runtime.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/safety/audit.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/safety/guards.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/safety/preview.py +0 -0
- {adloop-0.19.0 → adloop-0.19.2}/src/adloop/tracking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: adloop
|
|
3
|
-
Version: 0.19.
|
|
3
|
+
Version: 0.19.2
|
|
4
4
|
Summary: The AI command center for Google Ads, Reddit Ads, GA4, and tracking code.
|
|
5
5
|
Keywords: mcp,google-ads,reddit-ads,google-analytics,ga4,cursor,marketing
|
|
6
6
|
Author: Daniel Klose
|
|
@@ -255,7 +255,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
|
|
|
255
255
|
| `draft_callouts` | Create campaign callout assets from 1-25 character text snippets. |
|
|
256
256
|
| `draft_structured_snippets` | Create campaign structured snippet assets using official header values and 3-10 snippet values. |
|
|
257
257
|
| `draft_image_assets` | Create campaign image assets from local files or public image URLs (PNG, JPEG, or GIF). |
|
|
258
|
-
| `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy —
|
|
258
|
+
| `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — flags BROAD match on campaigns without Smart Bidding as dangerous in the preview. |
|
|
259
259
|
| `add_negative_keywords` | Propose negative keywords directly on a campaign |
|
|
260
260
|
| `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
|
|
261
261
|
| `draft_key_event` | Mark a GA4 event as a key event (conversion) — the fix for "fires but isn't tracked as a conversion" |
|
|
@@ -314,8 +314,8 @@ AdLoop manages real ad spend, so safety is not optional.
|
|
|
314
314
|
- **Budget caps.** Configurable maximum daily budget — the server rejects anything above the cap.
|
|
315
315
|
- **Audit log.** Every operation (including dry runs) is logged to `~/.adloop/audit.log`.
|
|
316
316
|
- **New campaigns and ads are PAUSED.** Nothing goes live without manual enablement.
|
|
317
|
-
- **Destructive ops
|
|
318
|
-
- **Broad Match
|
|
317
|
+
- **Destructive ops are flagged for double confirmation.** Removing entities or large budget increases come back with extra warnings, and the AI is instructed to confirm twice.
|
|
318
|
+
- **Broad Match without Smart Bidding caught.** The #1 cause of wasted ad spend: `draft_campaign` refuses BROAD keywords unless the campaign uses Smart Bidding, and `draft_keywords` / `draft_ad_group` mark them as dangerous in the preview, so the AI has to raise it before anything is applied.
|
|
319
319
|
- **Pre-write validation.** Before any write, the AI checks bidding strategy, conversion tracking status, and quality scores. If the campaign is fundamentally broken, AdLoop warns you instead of making things worse.
|
|
320
320
|
- **Structured error handling.** All tools return actionable error messages with hints instead of raw exceptions. Auth errors include specific re-authorization steps.
|
|
321
321
|
- **API version pinning.** The Google Ads API version is pinned to prevent silent breaking changes from library updates. `health_check` warns when a newer version is available.
|
|
@@ -578,7 +578,7 @@ What's been shipped and what's next:
|
|
|
578
578
|
- ~~PyPI package~~ ✓ — `pip install adloop`
|
|
579
579
|
- ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version: no Google Cloud project, no API access application, connect Google in two clicks (EU-hosted, GDPR-first)
|
|
580
580
|
- ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
|
|
581
|
-
- ~~Behavioral eval suites~~ ✓ —
|
|
581
|
+
- ~~Behavioral eval suites~~ ✓ — prompt-and-expectation tests in `tests/evals/` covering read, write, tracking, and planning workflows
|
|
582
582
|
- ~~Google Tag Manager integration~~ ✓ — read tools for tags, triggers, variables, workspaces, and version history, plus the `audit_event_coverage` three-way join across codebase events, GTM tags, and GA4 actual fires
|
|
583
583
|
- **Community launch** — HN, Indie Hackers, r/cursor, Twitter
|
|
584
584
|
- **Video walkthrough**
|
|
@@ -228,7 +228,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
|
|
|
228
228
|
| `draft_callouts` | Create campaign callout assets from 1-25 character text snippets. |
|
|
229
229
|
| `draft_structured_snippets` | Create campaign structured snippet assets using official header values and 3-10 snippet values. |
|
|
230
230
|
| `draft_image_assets` | Create campaign image assets from local files or public image URLs (PNG, JPEG, or GIF). |
|
|
231
|
-
| `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy —
|
|
231
|
+
| `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — flags BROAD match on campaigns without Smart Bidding as dangerous in the preview. |
|
|
232
232
|
| `add_negative_keywords` | Propose negative keywords directly on a campaign |
|
|
233
233
|
| `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
|
|
234
234
|
| `draft_key_event` | Mark a GA4 event as a key event (conversion) — the fix for "fires but isn't tracked as a conversion" |
|
|
@@ -287,8 +287,8 @@ AdLoop manages real ad spend, so safety is not optional.
|
|
|
287
287
|
- **Budget caps.** Configurable maximum daily budget — the server rejects anything above the cap.
|
|
288
288
|
- **Audit log.** Every operation (including dry runs) is logged to `~/.adloop/audit.log`.
|
|
289
289
|
- **New campaigns and ads are PAUSED.** Nothing goes live without manual enablement.
|
|
290
|
-
- **Destructive ops
|
|
291
|
-
- **Broad Match
|
|
290
|
+
- **Destructive ops are flagged for double confirmation.** Removing entities or large budget increases come back with extra warnings, and the AI is instructed to confirm twice.
|
|
291
|
+
- **Broad Match without Smart Bidding caught.** The #1 cause of wasted ad spend: `draft_campaign` refuses BROAD keywords unless the campaign uses Smart Bidding, and `draft_keywords` / `draft_ad_group` mark them as dangerous in the preview, so the AI has to raise it before anything is applied.
|
|
292
292
|
- **Pre-write validation.** Before any write, the AI checks bidding strategy, conversion tracking status, and quality scores. If the campaign is fundamentally broken, AdLoop warns you instead of making things worse.
|
|
293
293
|
- **Structured error handling.** All tools return actionable error messages with hints instead of raw exceptions. Auth errors include specific re-authorization steps.
|
|
294
294
|
- **API version pinning.** The Google Ads API version is pinned to prevent silent breaking changes from library updates. `health_check` warns when a newer version is available.
|
|
@@ -551,7 +551,7 @@ What's been shipped and what's next:
|
|
|
551
551
|
- ~~PyPI package~~ ✓ — `pip install adloop`
|
|
552
552
|
- ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version: no Google Cloud project, no API access application, connect Google in two clicks (EU-hosted, GDPR-first)
|
|
553
553
|
- ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
|
|
554
|
-
- ~~Behavioral eval suites~~ ✓ —
|
|
554
|
+
- ~~Behavioral eval suites~~ ✓ — prompt-and-expectation tests in `tests/evals/` covering read, write, tracking, and planning workflows
|
|
555
555
|
- ~~Google Tag Manager integration~~ ✓ — read tools for tags, triggers, variables, workspaces, and version history, plus the `audit_event_coverage` three-way join across codebase events, GTM tags, and GA4 actual fires
|
|
556
556
|
- **Community launch** — HN, Indie Hackers, r/cursor, Twitter
|
|
557
557
|
- **Video walkthrough**
|
|
@@ -537,7 +537,6 @@ def draft_clear_custom_conversion_goal(
|
|
|
537
537
|
|
|
538
538
|
Call ``confirm_and_apply`` with the returned plan_id to execute.
|
|
539
539
|
"""
|
|
540
|
-
from adloop.ads.client import normalize_customer_id
|
|
541
540
|
from adloop.safety.guards import SafetyViolation, check_blocked_operation
|
|
542
541
|
from adloop.safety.preview import ChangePlan, store_plan
|
|
543
542
|
|
|
@@ -553,7 +552,6 @@ def draft_clear_custom_conversion_goal(
|
|
|
553
552
|
"details": ["campaign_id must be a numeric ID"],
|
|
554
553
|
}
|
|
555
554
|
|
|
556
|
-
cid = normalize_customer_id(customer_id or config.ads.customer_id)
|
|
557
555
|
state = _custom_goal_state(config, customer_id, campaign_id=campaign_id)
|
|
558
556
|
campaign = find_campaign(state, campaign_id)
|
|
559
557
|
if campaign is None:
|
|
@@ -373,7 +373,11 @@ def get_search_terms(
|
|
|
373
373
|
"""Get search terms report — what users actually typed before clicking ads."""
|
|
374
374
|
from adloop.ads.gaql import execute_query
|
|
375
375
|
|
|
376
|
-
|
|
376
|
+
# search_term_view requires an explicit date segment, so the default is
|
|
377
|
+
# DURING LAST_30_DAYS and a given range replaces it. ``_date_clause``
|
|
378
|
+
# prefixes its fragment with AND (the other read tools append it to a
|
|
379
|
+
# WHERE); here it is the only predicate, so the AND comes off.
|
|
380
|
+
where = _date_clause(date_range_start, date_range_end).removeprefix("AND ")
|
|
377
381
|
|
|
378
382
|
query = f"""
|
|
379
383
|
SELECT search_term_view.search_term,
|
|
@@ -381,35 +385,10 @@ def get_search_terms(
|
|
|
381
385
|
metrics.impressions, metrics.clicks,
|
|
382
386
|
metrics.cost_micros, metrics.conversions
|
|
383
387
|
FROM search_term_view
|
|
384
|
-
WHERE
|
|
385
|
-
{f"AND segments.date BETWEEN '{date_range_start}' AND '{date_range_end}'" if date_range_start and date_range_end else ""}
|
|
388
|
+
WHERE {where}
|
|
386
389
|
ORDER BY metrics.clicks DESC
|
|
387
390
|
LIMIT 200
|
|
388
391
|
"""
|
|
389
|
-
# search_term_view requires an explicit date segment, so we always
|
|
390
|
-
# include DURING LAST_30_DAYS as baseline and override if dates given.
|
|
391
|
-
if date_range_start and date_range_end:
|
|
392
|
-
query = f"""
|
|
393
|
-
SELECT search_term_view.search_term,
|
|
394
|
-
campaign.name, ad_group.name,
|
|
395
|
-
metrics.impressions, metrics.clicks,
|
|
396
|
-
metrics.cost_micros, metrics.conversions
|
|
397
|
-
FROM search_term_view
|
|
398
|
-
WHERE segments.date BETWEEN '{date_range_start}' AND '{date_range_end}'
|
|
399
|
-
ORDER BY metrics.clicks DESC
|
|
400
|
-
LIMIT 200
|
|
401
|
-
"""
|
|
402
|
-
else:
|
|
403
|
-
query = """
|
|
404
|
-
SELECT search_term_view.search_term,
|
|
405
|
-
campaign.name, ad_group.name,
|
|
406
|
-
metrics.impressions, metrics.clicks,
|
|
407
|
-
metrics.cost_micros, metrics.conversions
|
|
408
|
-
FROM search_term_view
|
|
409
|
-
WHERE segments.date DURING LAST_30_DAYS
|
|
410
|
-
ORDER BY metrics.clicks DESC
|
|
411
|
-
LIMIT 200
|
|
412
|
-
"""
|
|
413
392
|
|
|
414
393
|
rows = execute_query(config, customer_id, query)
|
|
415
394
|
currency_code = get_currency_code(config, customer_id)
|
|
@@ -2780,7 +2780,27 @@ def confirm_and_apply(
|
|
|
2780
2780
|
"objects an earlier step would create, so Google could "
|
|
2781
2781
|
"only validate the step(s) before them."
|
|
2782
2782
|
)
|
|
2783
|
-
|
|
2783
|
+
from adloop.runtime import deployment_mode as _deployment_mode
|
|
2784
|
+
|
|
2785
|
+
if forced_by_config and _deployment_mode() == "server":
|
|
2786
|
+
# Hosted: the flag is the workspace's live-changes setting,
|
|
2787
|
+
# switched in the dashboard. There is no config file to edit
|
|
2788
|
+
# and no server for the user to restart.
|
|
2789
|
+
response["dry_run_forced_by"] = "config.safety.require_dry_run"
|
|
2790
|
+
response["remediation"] = (
|
|
2791
|
+
"Live changes are switched off for this workspace (or for "
|
|
2792
|
+
"this client). The workspace owner can switch them on in "
|
|
2793
|
+
"the AdLoop Cloud dashboard under Settings → Safety. Passing "
|
|
2794
|
+
"dry_run=false on this tool will keep being overridden until "
|
|
2795
|
+
"then."
|
|
2796
|
+
)
|
|
2797
|
+
response["message"] = (
|
|
2798
|
+
"dry_run=false was IGNORED because live changes are switched "
|
|
2799
|
+
"off for this workspace. No changes were made. Retrying this "
|
|
2800
|
+
"tool with dry_run=false will not succeed until live changes "
|
|
2801
|
+
"are switched on in the dashboard."
|
|
2802
|
+
)
|
|
2803
|
+
elif forced_by_config:
|
|
2784
2804
|
# The caller passed dry_run=false but safety.require_dry_run
|
|
2785
2805
|
# forced it back on. Tell them exactly why and how to unlock
|
|
2786
2806
|
# real writes — without this, agents (e.g. Claude Code) retry
|
|
@@ -3867,7 +3887,6 @@ def _apply_create_campaign(client: object, cid: str, changes: dict) -> dict:
|
|
|
3867
3887
|
results = {}
|
|
3868
3888
|
num_keywords = len(kw_list)
|
|
3869
3889
|
num_geo = len(changes.get("geo_target_ids") or [])
|
|
3870
|
-
num_lang = len(changes.get("language_ids") or [])
|
|
3871
3890
|
for i, resp in enumerate(response.mutate_operation_responses):
|
|
3872
3891
|
rn = _extract_resource_name(resp)
|
|
3873
3892
|
if rn:
|
|
@@ -13,9 +13,11 @@ from pydantic import BeforeValidator
|
|
|
13
13
|
from adloop import diagnostics
|
|
14
14
|
from adloop.runtime import current_config
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
# openWorldHint: every tool talks to an external service (Google, Reddit,
|
|
17
|
+
# a live web page), which directories such as ChatGPT's require to be said.
|
|
18
|
+
_READONLY = ToolAnnotations(readOnlyHint=True, destructiveHint=False, openWorldHint=True)
|
|
19
|
+
_WRITE = ToolAnnotations(readOnlyHint=False, destructiveHint=False, openWorldHint=True)
|
|
20
|
+
_DESTRUCTIVE = ToolAnnotations(readOnlyHint=False, destructiveHint=True, openWorldHint=True)
|
|
19
21
|
|
|
20
22
|
# Toolset taxonomy: every tool carries exactly one of these tags (or "core",
|
|
21
23
|
# which survives every ADLOOP_TOOLSETS selection — health_check and
|
|
@@ -308,13 +310,20 @@ def _structured_error(fn_name: str, exc: Exception) -> dict:
|
|
|
308
310
|
}
|
|
309
311
|
|
|
310
312
|
if "deleted_client" in err_lower or "invalid_client" in err_lower:
|
|
313
|
+
from adloop.runtime import deployment_mode
|
|
314
|
+
|
|
311
315
|
return {
|
|
312
316
|
"error": (
|
|
313
317
|
"Authentication failed — the OAuth client behind your stored "
|
|
314
318
|
"credentials no longer exists or is invalid."
|
|
315
319
|
),
|
|
320
|
+
# On a hosted server the user never sees an OAuth client: the
|
|
321
|
+
# fix is reconnecting, and pointing at a product would be an ad.
|
|
316
322
|
"hint": (
|
|
317
|
-
"
|
|
323
|
+
"Reconnect Google in your AdLoop Cloud dashboard "
|
|
324
|
+
"(Settings → Google), then retry."
|
|
325
|
+
if deployment_mode() == "server"
|
|
326
|
+
else "If you set up AdLoop before v0.10 with its bundled "
|
|
318
327
|
"credentials: that shared Google Cloud project has been "
|
|
319
328
|
"retired. Fastest fix: AdLoop Cloud (https://getadloop.com) — "
|
|
320
329
|
"connect Google in two clicks, no Google Cloud project "
|
|
@@ -552,6 +561,8 @@ def run_ga4_report(
|
|
|
552
561
|
|
|
553
562
|
Date formats: "today", "yesterday", "7daysAgo", "28daysAgo", "90daysAgo", or "YYYY-MM-DD".
|
|
554
563
|
If property_id is empty, uses the default from config.
|
|
564
|
+
Queries the GA4 Data API. Dimensions and metrics:
|
|
565
|
+
https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema
|
|
555
566
|
"""
|
|
556
567
|
from adloop.ga4.reports import run_ga4_report as _impl
|
|
557
568
|
|
|
@@ -648,7 +659,7 @@ def run_gsc_report(
|
|
|
648
659
|
finding keyword opportunities, and cross-referencing with GA4 and Ads data.
|
|
649
660
|
|
|
650
661
|
site_url: the GSC property URL (e.g. "https://example.com/" or
|
|
651
|
-
"sc-domain:example.com"). Defaults to
|
|
662
|
+
"sc-domain:example.com"). Defaults to the configured Search Console site.
|
|
652
663
|
dimensions: one or more of ["query", "page", "country", "device", "date"].
|
|
653
664
|
Defaults to ["query"].
|
|
654
665
|
date_range_start / date_range_end: ISO dates (YYYY-MM-DD) or relative
|
|
@@ -660,6 +671,8 @@ def run_gsc_report(
|
|
|
660
671
|
[{"filters": [{"dimension": "query", "operator": "contains",
|
|
661
672
|
"expression": "analytics"}]}]
|
|
662
673
|
limit: maximum rows to return (default 100, max 25000).
|
|
674
|
+
Queries the Search Console API:
|
|
675
|
+
https://developers.google.com/webmaster-tools/v1/searchanalytics/query
|
|
663
676
|
"""
|
|
664
677
|
from adloop.gsc.reports import run_gsc_report as _impl
|
|
665
678
|
|
|
@@ -708,8 +721,9 @@ def analyze_page_speed(url: str, strategy: str = "mobile") -> dict:
|
|
|
708
721
|
def list_merchant_accounts() -> dict:
|
|
709
722
|
"""List Google Merchant Center accounts the connected user can access.
|
|
710
723
|
|
|
711
|
-
|
|
712
|
-
|
|
724
|
+
Returns each account's numeric ID, name and whether it is a test
|
|
725
|
+
account. Call it to find the account_id for get_merchant_feed_health;
|
|
726
|
+
not needed when merchant.account_id is set in the config.
|
|
713
727
|
"""
|
|
714
728
|
from adloop.merchant.read import list_merchant_accounts as _impl
|
|
715
729
|
|
|
@@ -899,9 +913,9 @@ def get_negative_keyword_lists(
|
|
|
899
913
|
) -> dict:
|
|
900
914
|
"""List all shared negative keyword lists (SharedSets) in the account.
|
|
901
915
|
|
|
902
|
-
Returns each list's ID, name, status, and keyword count.
|
|
903
|
-
|
|
904
|
-
|
|
916
|
+
Returns each list's ID, name, status, and keyword count. Useful before
|
|
917
|
+
propose_negative_keyword_list: a suitable list may already exist and
|
|
918
|
+
just need attaching to a campaign.
|
|
905
919
|
"""
|
|
906
920
|
from adloop.ads.read import get_negative_keyword_lists as _impl
|
|
907
921
|
|
|
@@ -1543,7 +1557,8 @@ def run_gaql(
|
|
|
1543
1557
|
"""Execute an arbitrary GAQL (Google Ads Query Language) query.
|
|
1544
1558
|
|
|
1545
1559
|
Use this for advanced queries not covered by the other tools.
|
|
1546
|
-
|
|
1560
|
+
Queries the Google Ads API. GAQL syntax and fields:
|
|
1561
|
+
https://developers.google.com/google-ads/api/docs/query/overview
|
|
1547
1562
|
|
|
1548
1563
|
format: "table" (default, readable), "json" (structured), "csv" (exportable)
|
|
1549
1564
|
"""
|
|
@@ -2125,8 +2140,8 @@ def add_to_negative_keyword_list(
|
|
|
2125
2140
|
|
|
2126
2141
|
Use this when a suitable list already exists and only needs more keywords
|
|
2127
2142
|
(instead of propose_negative_keyword_list, which creates a new list).
|
|
2128
|
-
|
|
2129
|
-
|
|
2143
|
+
The shared_set_id comes from get_negative_keyword_lists;
|
|
2144
|
+
get_negative_keyword_list_keywords shows the terms already in a list.
|
|
2130
2145
|
|
|
2131
2146
|
shared_set_id: numeric ID from get_negative_keyword_lists (shared_set.id).
|
|
2132
2147
|
keywords: list of keyword strings to append (duplicates in the input list
|
|
@@ -2625,14 +2640,12 @@ def confirm_and_apply(
|
|
|
2625
2640
|
to make real changes to the ad account (Google Ads or Reddit Ads — the
|
|
2626
2641
|
plan knows which platform it targets).
|
|
2627
2642
|
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
user edits the config file, sets 'require_dry_run: false', and restarts
|
|
2635
|
-
the AdLoop MCP server.
|
|
2643
|
+
Forced dry runs: when real changes are switched off (live changes off
|
|
2644
|
+
for the workspace in AdLoop Cloud, or safety.require_dry_run in a
|
|
2645
|
+
self-hosted config), dry_run=false is IGNORED and the result is still
|
|
2646
|
+
DRY_RUN_SUCCESS, with 'dry_run_forced_by' and 'remediation' fields.
|
|
2647
|
+
Pass the remediation on to the user and stop retrying: calling again
|
|
2648
|
+
with dry_run=false changes nothing until that setting is changed.
|
|
2636
2649
|
|
|
2637
2650
|
Two-phase apply: if 'safety.two_phase_apply: true' is set (always on
|
|
2638
2651
|
for AdLoop Cloud tenants), dry_run=false is REFUSED with status
|
|
@@ -2815,6 +2828,7 @@ def run_reddit_report(
|
|
|
2815
2828
|
(HOUR and DATE cannot be combined). Dates are account-local days.
|
|
2816
2829
|
filter: Reddit filter expression (e.g. "campaign_id==abc123").
|
|
2817
2830
|
Microcurrency fields are converted to currency amounts.
|
|
2831
|
+
Queries the Reddit Ads API: https://ads-api.reddit.com/docs/v3/
|
|
2818
2832
|
"""
|
|
2819
2833
|
from adloop.reddit.read import run_reddit_report as _impl
|
|
2820
2834
|
|
|
@@ -3734,8 +3748,8 @@ def get_brand_lists(
|
|
|
3734
3748
|
) -> dict:
|
|
3735
3749
|
"""List all brand lists (SharedSets of type BRANDS) in the account.
|
|
3736
3750
|
|
|
3737
|
-
Returns each list's ID, name, status, and member count.
|
|
3738
|
-
propose_brand_list so an existing list can be reused instead of duplicated.
|
|
3751
|
+
Returns each list's ID, name, status, and member count. Useful before
|
|
3752
|
+
propose_brand_list, so an existing list can be reused instead of duplicated.
|
|
3739
3753
|
|
|
3740
3754
|
Args:
|
|
3741
3755
|
customer_id: Ads account ID. Defaults to the configured account.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|