adloop 0.19.1__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.
Files changed (64) hide show
  1. {adloop-0.19.1 → adloop-0.19.2}/PKG-INFO +1 -1
  2. {adloop-0.19.1 → adloop-0.19.2}/pyproject.toml +1 -1
  3. {adloop-0.19.1 → adloop-0.19.2}/pyproject.toml.orig +1 -1
  4. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/write.py +21 -1
  5. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/server.py +15 -10
  6. {adloop-0.19.1 → adloop-0.19.2}/README.md +0 -0
  7. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/__init__.py +0 -0
  8. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/__main__.py +0 -0
  9. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/_mcp_patches.py +0 -0
  10. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/__init__.py +0 -0
  11. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/ai_max.py +0 -0
  12. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/brands.py +0 -0
  13. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/client.py +0 -0
  14. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/conversion_actions.py +0 -0
  15. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/currency.py +0 -0
  16. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/custom_conversion_goals.py +0 -0
  17. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/enums.py +0 -0
  18. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/forecast.py +0 -0
  19. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/gaql.py +0 -0
  20. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/image_fetch.py +0 -0
  21. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/pmax.py +0 -0
  22. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/read.py +0 -0
  23. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ads/validate_only.py +0 -0
  24. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/auth.py +0 -0
  25. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/cli.py +0 -0
  26. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/config.py +0 -0
  27. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/crossref.py +0 -0
  28. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/diagnostics.py +0 -0
  29. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ga4/__init__.py +0 -0
  30. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ga4/client.py +0 -0
  31. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ga4/reports.py +0 -0
  32. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ga4/tracking.py +0 -0
  33. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/ga4/write.py +0 -0
  34. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/gsc/__init__.py +0 -0
  35. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/gsc/client.py +0 -0
  36. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/gsc/reports.py +0 -0
  37. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/gtm/__init__.py +0 -0
  38. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/gtm/client.py +0 -0
  39. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/gtm/read.py +0 -0
  40. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/merchant/__init__.py +0 -0
  41. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/merchant/client.py +0 -0
  42. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/merchant/read.py +0 -0
  43. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/pagespeed.py +0 -0
  44. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/reddit/__init__.py +0 -0
  45. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/reddit/auth.py +0 -0
  46. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/reddit/client.py +0 -0
  47. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/reddit/read.py +0 -0
  48. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/reddit/schedule.py +0 -0
  49. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/reddit/write.py +0 -0
  50. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/__init__.py +0 -0
  51. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/adloop.md +0 -0
  52. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/commands/analyze-performance.md +0 -0
  53. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/commands/budget-plan.md +0 -0
  54. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/commands/create-ad.md +0 -0
  55. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/commands/create-campaign.md +0 -0
  56. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  57. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  58. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/rules_install.py +0 -0
  59. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/runtime.py +0 -0
  60. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/safety/__init__.py +0 -0
  61. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/safety/audit.py +0 -0
  62. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/safety/guards.py +0 -0
  63. {adloop-0.19.1 → adloop-0.19.2}/src/adloop/safety/preview.py +0 -0
  64. {adloop-0.19.1 → 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.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.19.1"
3
+ version = "0.19.2"
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.19.1"
3
+ version = "0.19.2"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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
- if forced_by_config:
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
@@ -310,13 +310,20 @@ def _structured_error(fn_name: str, exc: Exception) -> dict:
310
310
  }
311
311
 
312
312
  if "deleted_client" in err_lower or "invalid_client" in err_lower:
313
+ from adloop.runtime import deployment_mode
314
+
313
315
  return {
314
316
  "error": (
315
317
  "Authentication failed — the OAuth client behind your stored "
316
318
  "credentials no longer exists or is invalid."
317
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.
318
322
  "hint": (
319
- "If you set up AdLoop before v0.10 with its bundled "
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 "
320
327
  "credentials: that shared Google Cloud project has been "
321
328
  "retired. Fastest fix: AdLoop Cloud (https://getadloop.com) — "
322
329
  "connect Google in two clicks, no Google Cloud project "
@@ -652,7 +659,7 @@ def run_gsc_report(
652
659
  finding keyword opportunities, and cross-referencing with GA4 and Ads data.
653
660
 
654
661
  site_url: the GSC property URL (e.g. "https://example.com/" or
655
- "sc-domain:example.com"). Defaults to gsc.site_url in config.yaml.
662
+ "sc-domain:example.com"). Defaults to the configured Search Console site.
656
663
  dimensions: one or more of ["query", "page", "country", "device", "date"].
657
664
  Defaults to ["query"].
658
665
  date_range_start / date_range_end: ISO dates (YYYY-MM-DD) or relative
@@ -2633,14 +2640,12 @@ def confirm_and_apply(
2633
2640
  to make real changes to the ad account (Google Ads or Reddit Ads — the
2634
2641
  plan knows which platform it targets).
2635
2642
 
2636
- Config override: if 'safety.require_dry_run: true' is set in the user's
2637
- config file (default ~/.adloop/config.yaml), dry_run=false is IGNORED
2638
- and this tool will keep returning DRY_RUN_SUCCESS. When that happens the
2639
- response includes 'dry_run_forced_by', 'config_path', and 'remediation'
2640
- fields — surface those to the user verbatim and STOP retrying. Calling
2641
- this tool again with dry_run=false will not change anything until the
2642
- user edits the config file, sets 'require_dry_run: false', and restarts
2643
- 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.
2644
2649
 
2645
2650
  Two-phase apply: if 'safety.two_phase_apply: true' is set (always on
2646
2651
  for AdLoop Cloud tenants), dry_run=false is REFUSED with status
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