adloop 0.21.0__tar.gz → 0.22.0__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.21.0 → adloop-0.22.0}/PKG-INFO +19 -13
- {adloop-0.21.0 → adloop-0.22.0}/README.md +18 -12
- {adloop-0.21.0 → adloop-0.22.0}/pyproject.toml +1 -1
- {adloop-0.21.0 → adloop-0.22.0}/pyproject.toml.orig +1 -1
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/conversion_actions.py +32 -10
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/gaql.py +6 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/read.py +432 -3
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/write.py +19 -6
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/crossref.py +24 -8
- adloop-0.22.0/src/adloop/ga4/reports.py +513 -0
- adloop-0.22.0/src/adloop/ga4/tracking.py +89 -0
- adloop-0.22.0/src/adloop/ga4/write.py +239 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gsc/reports.py +89 -8
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gtm/read.py +424 -111
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gtm/write.py +213 -9
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/pagespeed.py +56 -8
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/reddit/read.py +59 -2
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/reddit/write.py +147 -1
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/adloop.md +49 -24
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/server.py +459 -51
- adloop-0.21.0/src/adloop/ga4/reports.py +0 -162
- adloop-0.21.0/src/adloop/ga4/tracking.py +0 -36
- adloop-0.21.0/src/adloop/ga4/write.py +0 -100
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/__main__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/ai_max.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/brands.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/client.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/currency.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/custom_conversion_goals.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/enums.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/forecast.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/image_fetch.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ads/validate_only.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/auth.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/cli.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/config.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/diagnostics.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/ga4/client.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gsc/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gsc/client.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gtm/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gtm/client.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/gtm/entities.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/merchant/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/merchant/client.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/merchant/read.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/reddit/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/reddit/auth.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/reddit/client.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/reddit/schedule.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/commands/analyze-performance.md +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules/commands/optimize-campaign.md +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/rules_install.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/runtime.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/safety/audit.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/safety/guards.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/safety/preview.py +0 -0
- {adloop-0.21.0 → adloop-0.22.0}/src/adloop/tracking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: adloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
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
|
|
@@ -67,7 +67,7 @@ Both versions run the same tools with the same safety model. The difference is w
|
|
|
67
67
|
| **Works with** | claude.ai, ChatGPT, Claude Code, Cursor, Gemini | Claude Code, Cursor, Claude Desktop, any local MCP client |
|
|
68
68
|
| **Where your data flows** | EU servers (Germany), GDPR-first, DPA included | 100% your machine — nothing leaves it |
|
|
69
69
|
| **Updates** | Automatic | `pip install -U adloop` |
|
|
70
|
-
| **Price** | Free plan, no card; [paid plans](https://getadloop.com/
|
|
70
|
+
| **Price** | Free plan, no card; [paid plans](https://getadloop.com/pricing) for more accounts and volume | Free forever (MIT) |
|
|
71
71
|
|
|
72
72
|
**Not sure? [Start with Cloud](https://getadloop.com)** — it's the fastest way to see what AdLoop can do, and it's the only way to use AdLoop from claude.ai or ChatGPT. Self-host when you want everything on your own machine or need to modify the code. And if you're here to hack on AdLoop itself: welcome, keep scrolling.
|
|
73
73
|
|
|
@@ -110,17 +110,19 @@ The best features come from real workflows. If you're using AdLoop and find your
|
|
|
110
110
|
| Tool | What It Does |
|
|
111
111
|
|------|-------------|
|
|
112
112
|
| `get_account_summaries` | List GA4 accounts and properties |
|
|
113
|
-
| `run_ga4_report` | Custom reports
|
|
113
|
+
| `run_ga4_report` | Custom reports: sessions, users, key events, page performance. Optional dimension and metric filters, ordering, paging, and a comparison period |
|
|
114
114
|
| `run_realtime_report` | Live data — verify tracking fires after deploys |
|
|
115
115
|
| `get_tracking_events` | All configured events and their volume |
|
|
116
|
+
| `list_key_events` | The property's key events (conversions) with counting method, create time and whether they can be deleted |
|
|
117
|
+
| `list_ga4_dimensions_and_metrics` | Every dimension and metric the property can report on, including custom ones, with search |
|
|
116
118
|
|
|
117
119
|
### Google Ads Read Tools
|
|
118
120
|
|
|
119
121
|
| Tool | What It Does |
|
|
120
122
|
|------|-------------|
|
|
121
123
|
| `list_accounts` | Discover accessible Ads accounts |
|
|
122
|
-
| `get_campaign_performance` | Campaign metrics
|
|
123
|
-
| `get_ad_performance` | Ad copy analysis
|
|
124
|
+
| `get_campaign_performance` | Campaign metrics: impressions, clicks, cost, conversions, CPA, Search impression share and share lost to budget/rank |
|
|
125
|
+
| `get_ad_performance` | Ad copy analysis: headlines, descriptions, CTR, policy approval status and topics |
|
|
124
126
|
| `get_keyword_performance` | Keywords — quality scores, competitive metrics |
|
|
125
127
|
| `get_search_terms` | What users actually searched before clicking |
|
|
126
128
|
| `get_negative_keywords` | List direct campaign-level negative keywords |
|
|
@@ -132,6 +134,7 @@ The best features come from real workflows. If you're using AdLoop and find your
|
|
|
132
134
|
| `get_pmax_assets` | Per-asset details for PMax — field type, serving status, content |
|
|
133
135
|
| `get_detailed_asset_performance` | Top-performing asset combinations — which headline+description+image combos Google selects most |
|
|
134
136
|
| `get_audience_performance` | Audience segment performance — remarketing, in-market, affinity, demographics |
|
|
137
|
+
| `get_change_history` | What changed before the drop: who changed what and when, through which client (UI, API, scripts, auto-applied recommendations), last 30 days |
|
|
135
138
|
| `get_demographic_targeting` | List demographic criteria (age/gender/parental status/income) on an ad group or campaign |
|
|
136
139
|
| `suggest_brands` | Resolve a brand name to the brands Google recognizes — brand ID, name, state, URLs |
|
|
137
140
|
| `check_brand_names` | Check a shortlist of brand names against Google's brand knowledge graph (max 25 per call) |
|
|
@@ -178,15 +181,16 @@ These tools read the live GTM container and join it with the codebase + GA4 to f
|
|
|
178
181
|
| `audit_event_coverage` | **The flagship.** Three-way join: codebase events ↔ GTM tags ↔ GA4 actual fires. For each event name in `expected_events`, returns one of 10 statuses (`ok`, `no_tag_no_fire`, `tag_paused`, `tag_active_but_not_firing`, `gtm_only_firing`, `ga4_only`, etc.) plus auto-generated insights for the gaps. |
|
|
179
182
|
| `list_gtm_accounts` | Discover accessible GTM accounts |
|
|
180
183
|
| `list_gtm_containers` | List containers under an account — returns numeric `container_id` (needed by other tools), public `GTM-XXXXXXX` ID, and usage context (web/iOS/Android/server) |
|
|
181
|
-
| `list_gtm_tags` | Every tag in the live container with parsed event names and resolved firing/blocking trigger names |
|
|
184
|
+
| `list_gtm_tags` | Every tag in the live container with parsed event names and resolved firing/blocking trigger names. Pass `workspace_id` to read a workspace's unpublished state instead, so a just-drafted tag is visible (also on `get_gtm_tag`, `list_gtm_triggers`, `get_gtm_trigger`, `list_gtm_variables`) |
|
|
182
185
|
| `get_gtm_tag` | Full raw config for a single tag — every parameter, firing/blocking triggers with filter conditions, priority, pause status, sampling |
|
|
183
186
|
| `list_gtm_triggers` | Every trigger with filter conditions parsed to readable text (e.g. `{{Page Path}} contains service-promotions`, `{{Form ID}} NOT contains wf-form-...`). Renders the `negate` flag explicitly. |
|
|
184
187
|
| `get_gtm_trigger` | Full trigger config + reverse lookup of every tag that uses it. Includes parsed `element_visibility` block (selector, on-screen ratio, firing frequency) for elementVisibility triggers and `group_member_trigger_ids` for triggerGroup types |
|
|
185
188
|
| `list_gtm_variables` | Custom variables (data layer, constants, JS) plus enabled built-in variables |
|
|
186
189
|
| `list_gtm_workspaces` | List drafts (workspaces) under a container — workspace IDs are needed by `get_gtm_workspace_diff` |
|
|
187
190
|
| `get_gtm_workspace_diff` | Drafted-but-not-published changes — common cause of "I edited a tag but nothing happened". Returns `is_clean: true` when nothing is pending. |
|
|
188
|
-
| `list_gtm_versions` |
|
|
189
|
-
| `get_gtm_version` |
|
|
191
|
+
| `list_gtm_versions` | Version history, newest first, with version IDs and entity counts. The Tag Manager API returns no timestamps or author for versions. |
|
|
192
|
+
| `get_gtm_version` | Name, notes and tag/trigger names for a single historical container version |
|
|
193
|
+
| `get_gtm_version_diff` | What a publish changed: added, removed and changed tags, triggers and variables with the changed fields, plus built-in variables enabled or disabled. Defaults to the live version against the one before it; two or three API calls. |
|
|
190
194
|
|
|
191
195
|
#### GTM write tools (opt-in)
|
|
192
196
|
|
|
@@ -198,13 +202,14 @@ Off by default. Set `gtm.write_enabled: true` in `~/.adloop/config.yaml` and res
|
|
|
198
202
|
| `draft_gtm_trigger` | Create a trigger, or update one by `trigger_id` (type is immutable). `custom_event_name` wires a `customEvent` trigger to a dataLayer event. |
|
|
199
203
|
| `draft_delete_gtm_entity` | Delete a workspace tag or trigger. Triggers still referenced by a tag are refused up front, with the tags listed. |
|
|
200
204
|
| `draft_publish_gtm_workspace` | Publish a workspace live. The preview lists every pending change, including edits other people made in the GTM UI. |
|
|
205
|
+
| `draft_rollback_gtm_version` | Republish an older container version live. The preview is the diff from the live version to the target; the plan pins the live version, so apply refuses if someone publishes in between. Workspaces are left as they are. |
|
|
201
206
|
|
|
202
207
|
Safety gates specific to GTM:
|
|
203
208
|
- **Custom HTML** runs arbitrary JavaScript on your site, so creating or editing an `html` tag — or publishing a workspace that adds or changes one — is refused unless `gtm.allow_custom_html: true` is also set. Pausing or deleting one is always allowed.
|
|
204
209
|
- **No stale writes.** Updates and deletes pin the entity's fingerprint from the preview, and publish pins the workspace's pending changes; if someone edits the container in between, apply refuses and you re-draft.
|
|
205
210
|
- Publishing stops on merge conflicts or GTM compiler errors. The dry run runs a Tag Manager **quick preview** first (Tag Manager has no validate-only mode), so compiler errors surface before a version is even created. That preview is a POST and stores a preview version in the container — nothing is published and no live tag changes; the rest of the dry run only reads.
|
|
206
|
-
- The publish
|
|
207
|
-
- Per-operation names for `safety.blocked_operations`: `gtm_create_tag`, `gtm_update_tag`, `gtm_delete_tag`, `gtm_create_trigger`, `gtm_update_trigger`, `gtm_delete_trigger`, `gtm_publish_workspace`.
|
|
211
|
+
- The publish and rollback results name the version they replaced (`previous_live_version_id`), so rolling back is one step: `draft_rollback_gtm_version` with that id. If the version was created but publishing it failed, the error carries both the created version id and the still-live one.
|
|
212
|
+
- Per-operation names for `safety.blocked_operations`: `gtm_create_tag`, `gtm_update_tag`, `gtm_delete_tag`, `gtm_create_trigger`, `gtm_update_trigger`, `gtm_delete_trigger`, `gtm_publish_workspace`, `gtm_rollback_version`.
|
|
208
213
|
|
|
209
214
|
The Google account also needs **Edit** permission on the container (and **Publish** to publish) under Admin → User Management.
|
|
210
215
|
|
|
@@ -215,13 +220,13 @@ The Google account also needs **Edit** permission on the container (and **Publis
|
|
|
215
220
|
| Tool | What It Does |
|
|
216
221
|
|------|-------------|
|
|
217
222
|
| `list_gsc_sites` | List Search Console properties the connected account can access |
|
|
218
|
-
| `run_gsc_report` | Organic search analytics
|
|
223
|
+
| `run_gsc_report` | Organic search analytics: clicks, impressions, CTR, position by query/page/country/device/date/searchAppearance/hour. Supports fresh data (`data_state="all"`), `aggregation_type`, and paging past 25,000 rows with `start_row` |
|
|
219
224
|
|
|
220
225
|
### Web Performance Tools
|
|
221
226
|
|
|
222
227
|
| Tool | What It Does |
|
|
223
228
|
|------|-------------|
|
|
224
|
-
| `analyze_page_speed` | PageSpeed Insights for landing pages
|
|
229
|
+
| `analyze_page_speed` | PageSpeed Insights for landing pages: Lighthouse score, Core Web Vitals, real-user CrUX data (falls back to origin-wide data, labelled as such, when the page has too little traffic), top fixes. No OAuth needed (optional API key). |
|
|
225
230
|
|
|
226
231
|
### Merchant Center Tools
|
|
227
232
|
|
|
@@ -280,6 +285,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
|
|
|
280
285
|
| `add_negative_keywords` | Propose negative keywords directly on a campaign |
|
|
281
286
|
| `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
|
|
282
287
|
| `draft_key_event` | Mark a GA4 event as a key event (conversion) — the fix for "fires but isn't tracked as a conversion" |
|
|
288
|
+
| `draft_delete_key_event` | Remove a GA4 key event. The event keeps firing but no longer counts as a conversion, for future data only |
|
|
283
289
|
| `draft_demographic_targeting` | Propose demographic criteria (age, gender, parental status, income) — exclusions by default |
|
|
284
290
|
| `propose_negative_keyword_list` | Draft a shared negative keyword list (SharedSet) and attach it to a campaign — reusable across multiple campaigns |
|
|
285
291
|
| `propose_brand_list` | Draft a brand list (SharedSet of type BRANDS) from Commercial KG MIDs and optionally attach it to campaigns — `negative=true` (default) excludes the brands, `false` restricts targeting to them |
|
|
@@ -614,7 +620,7 @@ MIT — see [LICENSE](LICENSE).
|
|
|
614
620
|
|
|
615
621
|
## Privacy
|
|
616
622
|
|
|
617
|
-
The open-source version runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy. AdLoop Cloud has its own [privacy policy](https://getadloop.com/
|
|
623
|
+
The open-source version runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy. AdLoop Cloud has its own [privacy policy](https://getadloop.com/privacy) and [DPA](https://getadloop.com/avv).
|
|
618
624
|
|
|
619
625
|
---
|
|
620
626
|
|
|
@@ -39,7 +39,7 @@ Both versions run the same tools with the same safety model. The difference is w
|
|
|
39
39
|
| **Works with** | claude.ai, ChatGPT, Claude Code, Cursor, Gemini | Claude Code, Cursor, Claude Desktop, any local MCP client |
|
|
40
40
|
| **Where your data flows** | EU servers (Germany), GDPR-first, DPA included | 100% your machine — nothing leaves it |
|
|
41
41
|
| **Updates** | Automatic | `pip install -U adloop` |
|
|
42
|
-
| **Price** | Free plan, no card; [paid plans](https://getadloop.com/
|
|
42
|
+
| **Price** | Free plan, no card; [paid plans](https://getadloop.com/pricing) for more accounts and volume | Free forever (MIT) |
|
|
43
43
|
|
|
44
44
|
**Not sure? [Start with Cloud](https://getadloop.com)** — it's the fastest way to see what AdLoop can do, and it's the only way to use AdLoop from claude.ai or ChatGPT. Self-host when you want everything on your own machine or need to modify the code. And if you're here to hack on AdLoop itself: welcome, keep scrolling.
|
|
45
45
|
|
|
@@ -82,17 +82,19 @@ The best features come from real workflows. If you're using AdLoop and find your
|
|
|
82
82
|
| Tool | What It Does |
|
|
83
83
|
|------|-------------|
|
|
84
84
|
| `get_account_summaries` | List GA4 accounts and properties |
|
|
85
|
-
| `run_ga4_report` | Custom reports
|
|
85
|
+
| `run_ga4_report` | Custom reports: sessions, users, key events, page performance. Optional dimension and metric filters, ordering, paging, and a comparison period |
|
|
86
86
|
| `run_realtime_report` | Live data — verify tracking fires after deploys |
|
|
87
87
|
| `get_tracking_events` | All configured events and their volume |
|
|
88
|
+
| `list_key_events` | The property's key events (conversions) with counting method, create time and whether they can be deleted |
|
|
89
|
+
| `list_ga4_dimensions_and_metrics` | Every dimension and metric the property can report on, including custom ones, with search |
|
|
88
90
|
|
|
89
91
|
### Google Ads Read Tools
|
|
90
92
|
|
|
91
93
|
| Tool | What It Does |
|
|
92
94
|
|------|-------------|
|
|
93
95
|
| `list_accounts` | Discover accessible Ads accounts |
|
|
94
|
-
| `get_campaign_performance` | Campaign metrics
|
|
95
|
-
| `get_ad_performance` | Ad copy analysis
|
|
96
|
+
| `get_campaign_performance` | Campaign metrics: impressions, clicks, cost, conversions, CPA, Search impression share and share lost to budget/rank |
|
|
97
|
+
| `get_ad_performance` | Ad copy analysis: headlines, descriptions, CTR, policy approval status and topics |
|
|
96
98
|
| `get_keyword_performance` | Keywords — quality scores, competitive metrics |
|
|
97
99
|
| `get_search_terms` | What users actually searched before clicking |
|
|
98
100
|
| `get_negative_keywords` | List direct campaign-level negative keywords |
|
|
@@ -104,6 +106,7 @@ The best features come from real workflows. If you're using AdLoop and find your
|
|
|
104
106
|
| `get_pmax_assets` | Per-asset details for PMax — field type, serving status, content |
|
|
105
107
|
| `get_detailed_asset_performance` | Top-performing asset combinations — which headline+description+image combos Google selects most |
|
|
106
108
|
| `get_audience_performance` | Audience segment performance — remarketing, in-market, affinity, demographics |
|
|
109
|
+
| `get_change_history` | What changed before the drop: who changed what and when, through which client (UI, API, scripts, auto-applied recommendations), last 30 days |
|
|
107
110
|
| `get_demographic_targeting` | List demographic criteria (age/gender/parental status/income) on an ad group or campaign |
|
|
108
111
|
| `suggest_brands` | Resolve a brand name to the brands Google recognizes — brand ID, name, state, URLs |
|
|
109
112
|
| `check_brand_names` | Check a shortlist of brand names against Google's brand knowledge graph (max 25 per call) |
|
|
@@ -150,15 +153,16 @@ These tools read the live GTM container and join it with the codebase + GA4 to f
|
|
|
150
153
|
| `audit_event_coverage` | **The flagship.** Three-way join: codebase events ↔ GTM tags ↔ GA4 actual fires. For each event name in `expected_events`, returns one of 10 statuses (`ok`, `no_tag_no_fire`, `tag_paused`, `tag_active_but_not_firing`, `gtm_only_firing`, `ga4_only`, etc.) plus auto-generated insights for the gaps. |
|
|
151
154
|
| `list_gtm_accounts` | Discover accessible GTM accounts |
|
|
152
155
|
| `list_gtm_containers` | List containers under an account — returns numeric `container_id` (needed by other tools), public `GTM-XXXXXXX` ID, and usage context (web/iOS/Android/server) |
|
|
153
|
-
| `list_gtm_tags` | Every tag in the live container with parsed event names and resolved firing/blocking trigger names |
|
|
156
|
+
| `list_gtm_tags` | Every tag in the live container with parsed event names and resolved firing/blocking trigger names. Pass `workspace_id` to read a workspace's unpublished state instead, so a just-drafted tag is visible (also on `get_gtm_tag`, `list_gtm_triggers`, `get_gtm_trigger`, `list_gtm_variables`) |
|
|
154
157
|
| `get_gtm_tag` | Full raw config for a single tag — every parameter, firing/blocking triggers with filter conditions, priority, pause status, sampling |
|
|
155
158
|
| `list_gtm_triggers` | Every trigger with filter conditions parsed to readable text (e.g. `{{Page Path}} contains service-promotions`, `{{Form ID}} NOT contains wf-form-...`). Renders the `negate` flag explicitly. |
|
|
156
159
|
| `get_gtm_trigger` | Full trigger config + reverse lookup of every tag that uses it. Includes parsed `element_visibility` block (selector, on-screen ratio, firing frequency) for elementVisibility triggers and `group_member_trigger_ids` for triggerGroup types |
|
|
157
160
|
| `list_gtm_variables` | Custom variables (data layer, constants, JS) plus enabled built-in variables |
|
|
158
161
|
| `list_gtm_workspaces` | List drafts (workspaces) under a container — workspace IDs are needed by `get_gtm_workspace_diff` |
|
|
159
162
|
| `get_gtm_workspace_diff` | Drafted-but-not-published changes — common cause of "I edited a tag but nothing happened". Returns `is_clean: true` when nothing is pending. |
|
|
160
|
-
| `list_gtm_versions` |
|
|
161
|
-
| `get_gtm_version` |
|
|
163
|
+
| `list_gtm_versions` | Version history, newest first, with version IDs and entity counts. The Tag Manager API returns no timestamps or author for versions. |
|
|
164
|
+
| `get_gtm_version` | Name, notes and tag/trigger names for a single historical container version |
|
|
165
|
+
| `get_gtm_version_diff` | What a publish changed: added, removed and changed tags, triggers and variables with the changed fields, plus built-in variables enabled or disabled. Defaults to the live version against the one before it; two or three API calls. |
|
|
162
166
|
|
|
163
167
|
#### GTM write tools (opt-in)
|
|
164
168
|
|
|
@@ -170,13 +174,14 @@ Off by default. Set `gtm.write_enabled: true` in `~/.adloop/config.yaml` and res
|
|
|
170
174
|
| `draft_gtm_trigger` | Create a trigger, or update one by `trigger_id` (type is immutable). `custom_event_name` wires a `customEvent` trigger to a dataLayer event. |
|
|
171
175
|
| `draft_delete_gtm_entity` | Delete a workspace tag or trigger. Triggers still referenced by a tag are refused up front, with the tags listed. |
|
|
172
176
|
| `draft_publish_gtm_workspace` | Publish a workspace live. The preview lists every pending change, including edits other people made in the GTM UI. |
|
|
177
|
+
| `draft_rollback_gtm_version` | Republish an older container version live. The preview is the diff from the live version to the target; the plan pins the live version, so apply refuses if someone publishes in between. Workspaces are left as they are. |
|
|
173
178
|
|
|
174
179
|
Safety gates specific to GTM:
|
|
175
180
|
- **Custom HTML** runs arbitrary JavaScript on your site, so creating or editing an `html` tag — or publishing a workspace that adds or changes one — is refused unless `gtm.allow_custom_html: true` is also set. Pausing or deleting one is always allowed.
|
|
176
181
|
- **No stale writes.** Updates and deletes pin the entity's fingerprint from the preview, and publish pins the workspace's pending changes; if someone edits the container in between, apply refuses and you re-draft.
|
|
177
182
|
- Publishing stops on merge conflicts or GTM compiler errors. The dry run runs a Tag Manager **quick preview** first (Tag Manager has no validate-only mode), so compiler errors surface before a version is even created. That preview is a POST and stores a preview version in the container — nothing is published and no live tag changes; the rest of the dry run only reads.
|
|
178
|
-
- The publish
|
|
179
|
-
- Per-operation names for `safety.blocked_operations`: `gtm_create_tag`, `gtm_update_tag`, `gtm_delete_tag`, `gtm_create_trigger`, `gtm_update_trigger`, `gtm_delete_trigger`, `gtm_publish_workspace`.
|
|
183
|
+
- The publish and rollback results name the version they replaced (`previous_live_version_id`), so rolling back is one step: `draft_rollback_gtm_version` with that id. If the version was created but publishing it failed, the error carries both the created version id and the still-live one.
|
|
184
|
+
- Per-operation names for `safety.blocked_operations`: `gtm_create_tag`, `gtm_update_tag`, `gtm_delete_tag`, `gtm_create_trigger`, `gtm_update_trigger`, `gtm_delete_trigger`, `gtm_publish_workspace`, `gtm_rollback_version`.
|
|
180
185
|
|
|
181
186
|
The Google account also needs **Edit** permission on the container (and **Publish** to publish) under Admin → User Management.
|
|
182
187
|
|
|
@@ -187,13 +192,13 @@ The Google account also needs **Edit** permission on the container (and **Publis
|
|
|
187
192
|
| Tool | What It Does |
|
|
188
193
|
|------|-------------|
|
|
189
194
|
| `list_gsc_sites` | List Search Console properties the connected account can access |
|
|
190
|
-
| `run_gsc_report` | Organic search analytics
|
|
195
|
+
| `run_gsc_report` | Organic search analytics: clicks, impressions, CTR, position by query/page/country/device/date/searchAppearance/hour. Supports fresh data (`data_state="all"`), `aggregation_type`, and paging past 25,000 rows with `start_row` |
|
|
191
196
|
|
|
192
197
|
### Web Performance Tools
|
|
193
198
|
|
|
194
199
|
| Tool | What It Does |
|
|
195
200
|
|------|-------------|
|
|
196
|
-
| `analyze_page_speed` | PageSpeed Insights for landing pages
|
|
201
|
+
| `analyze_page_speed` | PageSpeed Insights for landing pages: Lighthouse score, Core Web Vitals, real-user CrUX data (falls back to origin-wide data, labelled as such, when the page has too little traffic), top fixes. No OAuth needed (optional API key). |
|
|
197
202
|
|
|
198
203
|
### Merchant Center Tools
|
|
199
204
|
|
|
@@ -252,6 +257,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
|
|
|
252
257
|
| `add_negative_keywords` | Propose negative keywords directly on a campaign |
|
|
253
258
|
| `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
|
|
254
259
|
| `draft_key_event` | Mark a GA4 event as a key event (conversion) — the fix for "fires but isn't tracked as a conversion" |
|
|
260
|
+
| `draft_delete_key_event` | Remove a GA4 key event. The event keeps firing but no longer counts as a conversion, for future data only |
|
|
255
261
|
| `draft_demographic_targeting` | Propose demographic criteria (age, gender, parental status, income) — exclusions by default |
|
|
256
262
|
| `propose_negative_keyword_list` | Draft a shared negative keyword list (SharedSet) and attach it to a campaign — reusable across multiple campaigns |
|
|
257
263
|
| `propose_brand_list` | Draft a brand list (SharedSet of type BRANDS) from Commercial KG MIDs and optionally attach it to campaigns — `negative=true` (default) excludes the brands, `false` restricts targeting to them |
|
|
@@ -586,7 +592,7 @@ MIT — see [LICENSE](LICENSE).
|
|
|
586
592
|
|
|
587
593
|
## Privacy
|
|
588
594
|
|
|
589
|
-
The open-source version runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy. AdLoop Cloud has its own [privacy policy](https://getadloop.com/
|
|
595
|
+
The open-source version runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy. AdLoop Cloud has its own [privacy policy](https://getadloop.com/privacy) and [DPA](https://getadloop.com/avv).
|
|
590
596
|
|
|
591
597
|
---
|
|
592
598
|
|
|
@@ -1142,17 +1142,35 @@ def _parse_call_conversion_csv(
|
|
|
1142
1142
|
|
|
1143
1143
|
|
|
1144
1144
|
def _redact_caller_id(caller_id: str) -> str:
|
|
1145
|
-
"""Mask an E.164 phone for display/logging:
|
|
1146
|
-
|
|
1145
|
+
"""Mask an E.164 phone for display/logging: the country code plus the last
|
|
1146
|
+
two digits. e.g. '+14155550142' -> '+1***42'.
|
|
1147
|
+
|
|
1148
|
+
Two trailing digits are enough to sanity-check that the right number was
|
|
1149
|
+
parsed. Everything between them is what would make the hint worth
|
|
1150
|
+
attacking, and this string reaches the preview, the model's context and the
|
|
1151
|
+
audit log.
|
|
1147
1152
|
"""
|
|
1148
1153
|
s = (caller_id or "").strip()
|
|
1149
1154
|
if not s:
|
|
1150
1155
|
return ""
|
|
1151
|
-
if len(s)
|
|
1156
|
+
if not s.startswith("+") or len(s) < 5:
|
|
1152
1157
|
return "***"
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1158
|
+
return f"+{_dialling_country_code(s)}***{s[-2:]}"
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
def _dialling_country_code(e164: str) -> str:
|
|
1162
|
+
"""The dialling country code of an E.164 number, "" when it is unknown.
|
|
1163
|
+
|
|
1164
|
+
libphonenumber knows where the country code ends (`+1` vs `+49` vs `+49`
|
|
1165
|
+
inside `+49151…`); a hand-rolled slice cannot. Failing to resolve it only
|
|
1166
|
+
costs the prefix of a display hint, so this never raises.
|
|
1167
|
+
"""
|
|
1168
|
+
try:
|
|
1169
|
+
import phonenumbers
|
|
1170
|
+
|
|
1171
|
+
return str(phonenumbers.parse(e164, None).country_code or "")
|
|
1172
|
+
except Exception: # noqa: BLE001 — display helper, never fatal
|
|
1173
|
+
return ""
|
|
1156
1174
|
|
|
1157
1175
|
|
|
1158
1176
|
def draft_upload_call_conversions(
|
|
@@ -2417,10 +2435,14 @@ def draft_upload_enhanced_conversions_for_leads(
|
|
|
2417
2435
|
),
|
|
2418
2436
|
"sample_rows": [
|
|
2419
2437
|
{
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2438
|
+
# A marker, not a prefix: the sample only has to show that
|
|
2439
|
+
# an identifier was there. 16 hex characters are 64 bits,
|
|
2440
|
+
# which is enough to confirm a guessed address from a word
|
|
2441
|
+
# list — in a preview that lands in a model's context and
|
|
2442
|
+
# in the audit log. The full hashes stay in
|
|
2443
|
+
# apply_only_payload, where the upload needs them.
|
|
2444
|
+
"email_sha256": "sha256:set" if r["email_sha256"] else "",
|
|
2445
|
+
"phone_sha256": "sha256:set" if r["phone_sha256"] else "",
|
|
2424
2446
|
"conversion_name": r["conversion_name"],
|
|
2425
2447
|
"conversion_value": r["conversion_value"],
|
|
2426
2448
|
"conversion_time": r["conversion_time"],
|
|
@@ -160,6 +160,12 @@ def _to_python(obj: object) -> object:
|
|
|
160
160
|
if type(obj) is not int and hasattr(obj, "name"):
|
|
161
161
|
return obj.name
|
|
162
162
|
return obj
|
|
163
|
+
# FieldMask (change_event.changed_fields): its JSON form is one
|
|
164
|
+
# comma-joined camelCase string, which hides the snake_case paths the
|
|
165
|
+
# rest of the API uses. Return the paths as they are.
|
|
166
|
+
descriptor = getattr(obj, "DESCRIPTOR", None)
|
|
167
|
+
if getattr(descriptor, "full_name", "") == "google.protobuf.FieldMask":
|
|
168
|
+
return list(obj.paths)
|
|
163
169
|
# Repeated fields (headlines, final_urls, etc.)
|
|
164
170
|
try:
|
|
165
171
|
return [_to_python(item) for item in obj]
|