adloop 0.26.0__tar.gz → 0.27.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.
Files changed (72) hide show
  1. {adloop-0.26.0 → adloop-0.27.0}/PKG-INFO +26 -3
  2. {adloop-0.26.0 → adloop-0.27.0}/README.md +25 -2
  3. {adloop-0.26.0 → adloop-0.27.0}/pyproject.toml +1 -1
  4. {adloop-0.26.0 → adloop-0.27.0}/pyproject.toml.orig +1 -1
  5. adloop-0.27.0/src/adloop/ads/pmax.py +721 -0
  6. adloop-0.27.0/src/adloop/ads/shopping.py +284 -0
  7. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/write.py +95 -43
  8. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/adloop.md +26 -8
  9. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/server.py +249 -11
  10. adloop-0.26.0/src/adloop/ads/pmax.py +0 -257
  11. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/__init__.py +0 -0
  12. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/__main__.py +0 -0
  13. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/_mcp_patches.py +0 -0
  14. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/__init__.py +0 -0
  15. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/ai_max.py +0 -0
  16. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/brands.py +0 -0
  17. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/client.py +0 -0
  18. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/conversion_actions.py +0 -0
  19. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/currency.py +0 -0
  20. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/custom_conversion_goals.py +0 -0
  21. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/enums.py +0 -0
  22. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/forecast.py +0 -0
  23. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/gaql.py +0 -0
  24. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/image_fetch.py +0 -0
  25. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/read.py +0 -0
  26. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ads/validate_only.py +0 -0
  27. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/auth.py +0 -0
  28. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/cli.py +0 -0
  29. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/config.py +0 -0
  30. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/crossref.py +0 -0
  31. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/diagnostics.py +0 -0
  32. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ga4/__init__.py +0 -0
  33. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ga4/client.py +0 -0
  34. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ga4/reports.py +0 -0
  35. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ga4/tracking.py +0 -0
  36. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/ga4/write.py +0 -0
  37. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gsc/__init__.py +0 -0
  38. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gsc/client.py +0 -0
  39. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gsc/reports.py +0 -0
  40. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gtm/__init__.py +0 -0
  41. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gtm/client.py +0 -0
  42. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gtm/entities.py +0 -0
  43. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gtm/read.py +0 -0
  44. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/gtm/write.py +0 -0
  45. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/merchant/__init__.py +0 -0
  46. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/merchant/client.py +0 -0
  47. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/merchant/read.py +0 -0
  48. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/pagespeed.py +0 -0
  49. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/reddit/__init__.py +0 -0
  50. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/reddit/auth.py +0 -0
  51. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/reddit/client.py +0 -0
  52. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/reddit/read.py +0 -0
  53. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/reddit/schedule.py +0 -0
  54. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/reddit/write.py +0 -0
  55. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/__init__.py +0 -0
  56. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/commands/analyze-performance.md +0 -0
  57. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/commands/budget-plan.md +0 -0
  58. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/commands/create-ad.md +0 -0
  59. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/commands/create-campaign.md +0 -0
  60. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  61. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  62. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/rules_install.py +0 -0
  63. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/runtime.py +0 -0
  64. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/safety/__init__.py +0 -0
  65. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/safety/approval.py +0 -0
  66. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/safety/approval_card.html +0 -0
  67. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/safety/audit.py +0 -0
  68. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/safety/guards.py +0 -0
  69. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/safety/preview.py +0 -0
  70. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/tracking.py +0 -0
  71. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/upload_card.html +0 -0
  72. {adloop-0.26.0 → adloop-0.27.0}/src/adloop/uploads.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: adloop
3
- Version: 0.26.0
3
+ Version: 0.27.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
@@ -41,6 +41,7 @@ Description-Content-Type: text/markdown
41
41
  [![Google Ads API](https://img.shields.io/badge/Google%20Ads-API%20v25-4285F4.svg?logo=google-ads&logoColor=white)](https://developers.google.com/google-ads/api/docs/start)
42
42
  [![GA4 Data API](https://img.shields.io/badge/GA4-Data%20API-E37400.svg?logo=google-analytics&logoColor=white)](https://developers.google.com/analytics/devguides/reporting/data/v1)
43
43
  [![GitHub stars](https://img.shields.io/github/stars/kLOsk/adloop?style=social)](https://github.com/kLOsk/adloop)
44
+ [![Smithery](https://img.shields.io/badge/Smithery-adloop-FF5601.svg)](https://smithery.ai/servers/adloop/adloop)
44
45
 
45
46
  An MCP server that gives your AI assistant read + write access to Google Ads, Reddit Ads and GA4 — with safety guardrails that prevent accidental spend.
46
47
 
@@ -132,6 +133,7 @@ The best features come from real workflows. If you're using AdLoop and find your
132
133
  | `get_recommendations` | Google's auto-generated recommendations with type, estimated impact, and campaign context |
133
134
  | `get_pmax_performance` | Performance Max campaign metrics with network breakdown + asset group ad strength |
134
135
  | `get_pmax_assets` | Per-asset details for PMax — field type, serving status, content |
136
+ | `get_asset_group_signals` | A PMax asset group's search themes and audience signal, plus the audiences available for it |
135
137
  | `get_detailed_asset_performance` | Top-performing asset combinations — which headline+description+image combos Google selects most |
136
138
  | `get_audience_performance` | Audience segment performance — remarketing, in-market, affinity, demographics |
137
139
  | `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 |
@@ -262,6 +264,19 @@ A second ad platform, same safety model. Reddit is a separate connection: its ow
262
264
 
263
265
  > **Setup for Reddit Ads tools** — In Reddit Ads Manager open **Business Manager → Developer Application → Create app** (business admins only; no approval wait). Register the redirect URL exactly as `http://localhost:8765/callback`, then run `adloop init` and complete the Reddit Ads step: it opens Reddit's consent page (scopes `adsread` + `adsedit`, permanent grant), stores the refresh token at `~/.adloop/reddit_token.json`, and lets you pick the default ad account. Reddit rate-limits per user (reporting: 60 requests/min) and requires a descriptive User-Agent, which AdLoop builds from your app id and Reddit username. Reddit has no validate-only mode, so `confirm_and_apply(dry_run=true)` re-reads the target and re-checks the safety caps instead.
264
266
 
267
+ ### Image Uploads
268
+
269
+ An image on your computer, or one you attached in the chat, has no public URL, and an AI app cannot hand the file to a tool. `upload_images` gives it a way in.
270
+
271
+ <p align="center"><img src="https://raw.githubusercontent.com/kLOsk/adloop/main/assets/upload-card.gif" alt="An AdLoop upload card in an AI chat: the user drags a banner onto it, the card shows the upload id, posts it into the chat, and the AI drafts the image asset" width="760"></p>
272
+
273
+ | Tool | What It Does |
274
+ |------|-------------|
275
+ | `upload_images` | Shows an upload card in apps with MCP Apps (drop PNG, JPEG or GIF up to 5 MB); on AdLoop Cloud in other apps it returns a one-time upload link, which a coding agent can also use with `curl -T` |
276
+ | `list_uploads` | The uploads that have not expired yet (kept 24 hours), with id, dimensions and size |
277
+
278
+ The upload ids go into `draft_image_assets(upload_ids=[...])`, or as `upload:<id>` into the image fields of `draft_reddit_ad` on AdLoop Cloud.
279
+
265
280
  ### Planning Tools
266
281
 
267
282
  | Tool | What It Does |
@@ -276,13 +291,17 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
276
291
  | Tool | What It Does |
277
292
  |------|-------------|
278
293
  | `draft_campaign` | Create a full campaign structure — budget + campaign (PAUSED) + ad group + optional keywords. Supports Search partners, display expansion, and `max_cpc` for either MANUAL_CPC initial ad-group bids or TARGET_SPEND (Maximize Clicks) CPC caps. |
294
+ | `draft_shopping_campaign` | Create a Standard Shopping campaign for a linked Merchant Center feed — budget, campaign (PAUSED), product ad group, Shopping ad and an "All products" group in one atomic request. Manual CPC, Maximize clicks or target ROAS. |
295
+ | `draft_asset_group_assets` | Add and remove Performance Max assets (text, images incl. uploads, YouTube videos) in one change, so replacing an asset never drops a group below Google's minimums |
296
+ | `draft_asset_group_signals` | Add search themes or set the audience signal of a PMax asset group |
297
+ | `draft_pmax_product_exclusions` | Exclude products from a retail PMax asset group by item id, brand or custom label |
279
298
  | `update_campaign` | Modify existing campaign settings — bidding, budget, geo/language targeting, Search partners, display expansion, and TARGET_SPEND (Maximize Clicks) `max_cpc` caps. |
280
299
  | `draft_ad_group` | Create a paused SEARCH_STANDARD ad group inside an existing campaign, with optional MANUAL_CPC `max_cpc`. |
281
300
  | `update_ad_group` | Update an ad group name and/or MANUAL_CPC `max_cpc`. Use `pause_entity` / `enable_entity` for ad-group status changes. |
282
301
  | `draft_responsive_search_ad` | Create RSA preview (3-15 headlines ≤30 chars, 2-4 descriptions ≤90 chars). Warns if headline/description count is below best practice. |
283
302
  | `draft_callouts` | Create campaign callout assets from 1-25 character text snippets. |
284
303
  | `draft_structured_snippets` | Create campaign structured snippet assets using official header values and 3-10 snippet values. |
285
- | `draft_image_assets` | Create campaign image assets from local files or public image URLs (PNG, JPEG, or GIF). |
304
+ | `draft_image_assets` | Create campaign image assets from local files, public image URLs or uploads (PNG, JPEG, or GIF). |
286
305
  | `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. |
287
306
  | `add_negative_keywords` | Propose negative keywords directly on a campaign |
288
307
  | `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
@@ -301,7 +320,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
301
320
  | `draft_update_custom_conversion_goal` | Rename a custom conversion goal and/or replace its action list (list replace, not append) |
302
321
  | `draft_assign_custom_conversion_goal` | Point a campaign at a custom conversion goal (`goal_config_level = CAMPAIGN`) |
303
322
  | `draft_clear_custom_conversion_goal` | Put a campaign back on the account-level goals (rollback) |
304
- | `pause_entity` | Pause a campaign, ad group, ad, or keyword |
323
+ | `pause_entity` | Pause a campaign, ad group, ad, keyword or PMax asset group |
305
324
  | `enable_entity` | Re-enable a paused entity |
306
325
  | `remove_entity` | Permanently remove an entity (irreversible — prefers pause). Supports keywords, negative keywords, ads, ad groups, campaigns. |
307
326
  | `confirm_and_apply` | Execute a previously previewed change |
@@ -337,6 +356,10 @@ AdLoop includes pre-built slash commands in `.claude/commands/` for common workf
337
356
 
338
357
  AdLoop manages real ad spend, so safety is not optional.
339
358
 
359
+ <p align="center"><img src="https://raw.githubusercontent.com/kLOsk/adloop/main/assets/approval-card.gif" alt="An AdLoop approval card in an AI chat: the user clicks Test run, Google validates the change, then Apply makes it live and the card reports back to the chat" width="760"></p>
360
+
361
+ - **Approval cards.** In apps that show MCP Apps (Claude, ChatGPT, VS Code and others), every change appears as a card with Test run and Apply. Only the click applies it: the AI's own `confirm_and_apply(dry_run=false)` is refused with `APPROVE_ON_CARD`, whatever it was told in the chat.
362
+ - **Confirmation dialogs.** Apps without cards that support elicitation (Claude Code, for example) get a dialog from AdLoop itself before every real change. The AI cannot answer it; declining changes nothing.
340
363
  - **Two-step writes.** Every mutation returns a preview first. A separate `confirm_and_apply` call is required to execute.
341
364
  - **Dry-run by default.** Even `confirm_and_apply` defaults to `dry_run=true`. Real changes require explicit `dry_run=false`.
342
365
  - **Two-phase apply (optional).** With `safety.two_phase_apply: true`, `confirm_and_apply` refuses `dry_run=false` until the plan has completed one dry-run pass — preview-then-apply becomes server-enforced instead of a convention.
@@ -13,6 +13,7 @@
13
13
  [![Google Ads API](https://img.shields.io/badge/Google%20Ads-API%20v25-4285F4.svg?logo=google-ads&logoColor=white)](https://developers.google.com/google-ads/api/docs/start)
14
14
  [![GA4 Data API](https://img.shields.io/badge/GA4-Data%20API-E37400.svg?logo=google-analytics&logoColor=white)](https://developers.google.com/analytics/devguides/reporting/data/v1)
15
15
  [![GitHub stars](https://img.shields.io/github/stars/kLOsk/adloop?style=social)](https://github.com/kLOsk/adloop)
16
+ [![Smithery](https://img.shields.io/badge/Smithery-adloop-FF5601.svg)](https://smithery.ai/servers/adloop/adloop)
16
17
 
17
18
  An MCP server that gives your AI assistant read + write access to Google Ads, Reddit Ads and GA4 — with safety guardrails that prevent accidental spend.
18
19
 
@@ -104,6 +105,7 @@ The best features come from real workflows. If you're using AdLoop and find your
104
105
  | `get_recommendations` | Google's auto-generated recommendations with type, estimated impact, and campaign context |
105
106
  | `get_pmax_performance` | Performance Max campaign metrics with network breakdown + asset group ad strength |
106
107
  | `get_pmax_assets` | Per-asset details for PMax — field type, serving status, content |
108
+ | `get_asset_group_signals` | A PMax asset group's search themes and audience signal, plus the audiences available for it |
107
109
  | `get_detailed_asset_performance` | Top-performing asset combinations — which headline+description+image combos Google selects most |
108
110
  | `get_audience_performance` | Audience segment performance — remarketing, in-market, affinity, demographics |
109
111
  | `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 |
@@ -234,6 +236,19 @@ A second ad platform, same safety model. Reddit is a separate connection: its ow
234
236
 
235
237
  > **Setup for Reddit Ads tools** — In Reddit Ads Manager open **Business Manager → Developer Application → Create app** (business admins only; no approval wait). Register the redirect URL exactly as `http://localhost:8765/callback`, then run `adloop init` and complete the Reddit Ads step: it opens Reddit's consent page (scopes `adsread` + `adsedit`, permanent grant), stores the refresh token at `~/.adloop/reddit_token.json`, and lets you pick the default ad account. Reddit rate-limits per user (reporting: 60 requests/min) and requires a descriptive User-Agent, which AdLoop builds from your app id and Reddit username. Reddit has no validate-only mode, so `confirm_and_apply(dry_run=true)` re-reads the target and re-checks the safety caps instead.
236
238
 
239
+ ### Image Uploads
240
+
241
+ An image on your computer, or one you attached in the chat, has no public URL, and an AI app cannot hand the file to a tool. `upload_images` gives it a way in.
242
+
243
+ <p align="center"><img src="https://raw.githubusercontent.com/kLOsk/adloop/main/assets/upload-card.gif" alt="An AdLoop upload card in an AI chat: the user drags a banner onto it, the card shows the upload id, posts it into the chat, and the AI drafts the image asset" width="760"></p>
244
+
245
+ | Tool | What It Does |
246
+ |------|-------------|
247
+ | `upload_images` | Shows an upload card in apps with MCP Apps (drop PNG, JPEG or GIF up to 5 MB); on AdLoop Cloud in other apps it returns a one-time upload link, which a coding agent can also use with `curl -T` |
248
+ | `list_uploads` | The uploads that have not expired yet (kept 24 hours), with id, dimensions and size |
249
+
250
+ The upload ids go into `draft_image_assets(upload_ids=[...])`, or as `upload:<id>` into the image fields of `draft_reddit_ad` on AdLoop Cloud.
251
+
237
252
  ### Planning Tools
238
253
 
239
254
  | Tool | What It Does |
@@ -248,13 +263,17 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
248
263
  | Tool | What It Does |
249
264
  |------|-------------|
250
265
  | `draft_campaign` | Create a full campaign structure — budget + campaign (PAUSED) + ad group + optional keywords. Supports Search partners, display expansion, and `max_cpc` for either MANUAL_CPC initial ad-group bids or TARGET_SPEND (Maximize Clicks) CPC caps. |
266
+ | `draft_shopping_campaign` | Create a Standard Shopping campaign for a linked Merchant Center feed — budget, campaign (PAUSED), product ad group, Shopping ad and an "All products" group in one atomic request. Manual CPC, Maximize clicks or target ROAS. |
267
+ | `draft_asset_group_assets` | Add and remove Performance Max assets (text, images incl. uploads, YouTube videos) in one change, so replacing an asset never drops a group below Google's minimums |
268
+ | `draft_asset_group_signals` | Add search themes or set the audience signal of a PMax asset group |
269
+ | `draft_pmax_product_exclusions` | Exclude products from a retail PMax asset group by item id, brand or custom label |
251
270
  | `update_campaign` | Modify existing campaign settings — bidding, budget, geo/language targeting, Search partners, display expansion, and TARGET_SPEND (Maximize Clicks) `max_cpc` caps. |
252
271
  | `draft_ad_group` | Create a paused SEARCH_STANDARD ad group inside an existing campaign, with optional MANUAL_CPC `max_cpc`. |
253
272
  | `update_ad_group` | Update an ad group name and/or MANUAL_CPC `max_cpc`. Use `pause_entity` / `enable_entity` for ad-group status changes. |
254
273
  | `draft_responsive_search_ad` | Create RSA preview (3-15 headlines ≤30 chars, 2-4 descriptions ≤90 chars). Warns if headline/description count is below best practice. |
255
274
  | `draft_callouts` | Create campaign callout assets from 1-25 character text snippets. |
256
275
  | `draft_structured_snippets` | Create campaign structured snippet assets using official header values and 3-10 snippet values. |
257
- | `draft_image_assets` | Create campaign image assets from local files or public image URLs (PNG, JPEG, or GIF). |
276
+ | `draft_image_assets` | Create campaign image assets from local files, public image URLs or uploads (PNG, JPEG, or GIF). |
258
277
  | `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
278
  | `add_negative_keywords` | Propose negative keywords directly on a campaign |
260
279
  | `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
@@ -273,7 +292,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
273
292
  | `draft_update_custom_conversion_goal` | Rename a custom conversion goal and/or replace its action list (list replace, not append) |
274
293
  | `draft_assign_custom_conversion_goal` | Point a campaign at a custom conversion goal (`goal_config_level = CAMPAIGN`) |
275
294
  | `draft_clear_custom_conversion_goal` | Put a campaign back on the account-level goals (rollback) |
276
- | `pause_entity` | Pause a campaign, ad group, ad, or keyword |
295
+ | `pause_entity` | Pause a campaign, ad group, ad, keyword or PMax asset group |
277
296
  | `enable_entity` | Re-enable a paused entity |
278
297
  | `remove_entity` | Permanently remove an entity (irreversible — prefers pause). Supports keywords, negative keywords, ads, ad groups, campaigns. |
279
298
  | `confirm_and_apply` | Execute a previously previewed change |
@@ -309,6 +328,10 @@ AdLoop includes pre-built slash commands in `.claude/commands/` for common workf
309
328
 
310
329
  AdLoop manages real ad spend, so safety is not optional.
311
330
 
331
+ <p align="center"><img src="https://raw.githubusercontent.com/kLOsk/adloop/main/assets/approval-card.gif" alt="An AdLoop approval card in an AI chat: the user clicks Test run, Google validates the change, then Apply makes it live and the card reports back to the chat" width="760"></p>
332
+
333
+ - **Approval cards.** In apps that show MCP Apps (Claude, ChatGPT, VS Code and others), every change appears as a card with Test run and Apply. Only the click applies it: the AI's own `confirm_and_apply(dry_run=false)` is refused with `APPROVE_ON_CARD`, whatever it was told in the chat.
334
+ - **Confirmation dialogs.** Apps without cards that support elicitation (Claude Code, for example) get a dialog from AdLoop itself before every real change. The AI cannot answer it; declining changes nothing.
312
335
  - **Two-step writes.** Every mutation returns a preview first. A separate `confirm_and_apply` call is required to execute.
313
336
  - **Dry-run by default.** Even `confirm_and_apply` defaults to `dry_run=true`. Real changes require explicit `dry_run=false`.
314
337
  - **Two-phase apply (optional).** With `safety.two_phase_apply: true`, `confirm_and_apply` refuses `dry_run=false` until the plan has completed one dry-run pass — preview-then-apply becomes server-enforced instead of a convention.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.26.0"
3
+ version = "0.27.0"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.26.0"
3
+ version = "0.27.0"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [