adloop 0.18.0__tar.gz → 0.18.1__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 (63) hide show
  1. {adloop-0.18.0 → adloop-0.18.1}/PKG-INFO +1 -1
  2. {adloop-0.18.0 → adloop-0.18.1}/pyproject.toml +1 -1
  3. {adloop-0.18.0 → adloop-0.18.1}/pyproject.toml.orig +1 -1
  4. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/cli.py +9 -0
  5. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/config.py +10 -0
  6. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/merchant/read.py +13 -1
  7. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/adloop.md +1 -1
  8. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/server.py +2 -1
  9. {adloop-0.18.0 → adloop-0.18.1}/README.md +0 -0
  10. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/__init__.py +0 -0
  11. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/__main__.py +0 -0
  12. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/_mcp_patches.py +0 -0
  13. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/__init__.py +0 -0
  14. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/ai_max.py +0 -0
  15. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/brands.py +0 -0
  16. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/client.py +0 -0
  17. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/conversion_actions.py +0 -0
  18. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/currency.py +0 -0
  19. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/custom_conversion_goals.py +0 -0
  20. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/enums.py +0 -0
  21. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/forecast.py +0 -0
  22. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/gaql.py +0 -0
  23. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/pmax.py +0 -0
  24. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/read.py +0 -0
  25. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/validate_only.py +0 -0
  26. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ads/write.py +0 -0
  27. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/auth.py +0 -0
  28. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/crossref.py +0 -0
  29. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/diagnostics.py +0 -0
  30. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ga4/__init__.py +0 -0
  31. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ga4/client.py +0 -0
  32. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ga4/reports.py +0 -0
  33. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ga4/tracking.py +0 -0
  34. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/ga4/write.py +0 -0
  35. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/gsc/__init__.py +0 -0
  36. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/gsc/client.py +0 -0
  37. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/gsc/reports.py +0 -0
  38. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/gtm/__init__.py +0 -0
  39. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/gtm/client.py +0 -0
  40. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/gtm/read.py +0 -0
  41. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/merchant/__init__.py +0 -0
  42. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/merchant/client.py +0 -0
  43. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/pagespeed.py +0 -0
  44. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/reddit/__init__.py +0 -0
  45. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/reddit/auth.py +0 -0
  46. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/reddit/client.py +0 -0
  47. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/reddit/read.py +0 -0
  48. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/reddit/schedule.py +0 -0
  49. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/reddit/write.py +0 -0
  50. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/__init__.py +0 -0
  51. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/commands/analyze-performance.md +0 -0
  52. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/commands/budget-plan.md +0 -0
  53. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/commands/create-ad.md +0 -0
  54. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/commands/create-campaign.md +0 -0
  55. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  56. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  57. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/rules_install.py +0 -0
  58. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/runtime.py +0 -0
  59. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/safety/__init__.py +0 -0
  60. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/safety/audit.py +0 -0
  61. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/safety/guards.py +0 -0
  62. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/safety/preview.py +0 -0
  63. {adloop-0.18.0 → adloop-0.18.1}/src/adloop/tracking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: adloop
3
- Version: 0.18.0
3
+ Version: 0.18.1
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.18.0"
3
+ version = "0.18.1"
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.18.0"
3
+ version = "0.18.1"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -156,6 +156,7 @@ def _generate_config_yaml(
156
156
  gsc_site_url: str = "",
157
157
  gtm_account_id: str = "",
158
158
  gtm_container_id: str = "",
159
+ merchant_account_id: str = "",
159
160
  pagespeed_api_key: str = "",
160
161
  reddit_client_id: str = "",
161
162
  reddit_client_secret: str = "",
@@ -206,6 +207,11 @@ def _generate_config_yaml(
206
207
  f' account_id: "{gtm_account_id}"',
207
208
  f' container_id: "{gtm_container_id}"',
208
209
  "",
210
+ "merchant:",
211
+ " # Default Merchant Center ID for get_merchant_feed_health",
212
+ " # (see list_merchant_accounts). Empty = pass account_id per call.",
213
+ f' account_id: "{merchant_account_id}"',
214
+ "",
209
215
  "pagespeed:",
210
216
  " # Optional API key for analyze_page_speed (keyless works, low quota).",
211
217
  f' api_key: "{pagespeed_api_key}"',
@@ -844,6 +850,9 @@ def _run_wizard_post_config(
844
850
  gsc_site_url=gsc_site_url,
845
851
  gtm_account_id=gtm_account_id,
846
852
  gtm_container_id=gtm_container_id,
853
+ # No Merchant discovery in the wizard; keep a hand-set default
854
+ # across re-runs instead of wiping it.
855
+ merchant_account_id=_existing("merchant", "account_id"), # type: ignore[operator]
847
856
  pagespeed_api_key=pagespeed_api_key,
848
857
  reddit_client_id=reddit_values["client_id"],
849
858
  reddit_client_secret=reddit_values["client_secret"],
@@ -43,6 +43,11 @@ class GtmConfig:
43
43
  container_id: str = ""
44
44
 
45
45
 
46
+ @dataclass
47
+ class MerchantConfig:
48
+ account_id: str = "" # numeric Merchant Center ID (see list_merchant_accounts)
49
+
50
+
46
51
  @dataclass
47
52
  class PageSpeedConfig:
48
53
  api_key: str = "" # optional — keyless PSI calls work but are rate-limited
@@ -85,6 +90,7 @@ class AdLoopConfig:
85
90
  ads: AdsConfig = field(default_factory=AdsConfig)
86
91
  gsc: GscConfig = field(default_factory=GscConfig)
87
92
  gtm: GtmConfig = field(default_factory=GtmConfig)
93
+ merchant: MerchantConfig = field(default_factory=MerchantConfig)
88
94
  pagespeed: PageSpeedConfig = field(default_factory=PageSpeedConfig)
89
95
  reddit: RedditConfig = field(default_factory=RedditConfig)
90
96
  safety: SafetyConfig = field(default_factory=SafetyConfig)
@@ -144,6 +150,7 @@ def load_config(config_path: str | None = None) -> AdLoopConfig:
144
150
  ads_raw = raw.get("ads", {})
145
151
  gsc_raw = raw.get("gsc", {})
146
152
  gtm_raw = raw.get("gtm", {})
153
+ merchant_raw = raw.get("merchant", {}) or {}
147
154
  pagespeed_raw = raw.get("pagespeed", {})
148
155
  reddit_raw = raw.get("reddit", {}) or {}
149
156
  safety_raw = raw.get("safety", {})
@@ -169,6 +176,9 @@ def load_config(config_path: str | None = None) -> AdLoopConfig:
169
176
  account_id=_text(gtm_raw, "account_id"),
170
177
  container_id=_text(gtm_raw, "container_id"),
171
178
  ),
179
+ merchant=MerchantConfig(
180
+ account_id=_text(merchant_raw, "account_id"),
181
+ ),
172
182
  pagespeed=PageSpeedConfig(
173
183
  api_key=_text(pagespeed_raw, "api_key"),
174
184
  ),
@@ -124,7 +124,19 @@ def get_merchant_feed_health(
124
124
  register_gcp,
125
125
  )
126
126
 
127
- account_id = str(account_id or "").strip()
127
+ account_id = str(account_id or "").strip() or config.merchant.account_id.strip()
128
+ if not account_id:
129
+ return {
130
+ "error": (
131
+ "account_id is required: no Merchant Center account was "
132
+ "passed and no default is configured."
133
+ ),
134
+ "hint": (
135
+ "Call list_merchant_accounts to find the numeric Merchant "
136
+ "Center ID and pass it as account_id, or set "
137
+ "merchant.account_id in the config to make it the default."
138
+ ),
139
+ }
128
140
  if not account_id.isdigit():
129
141
  return {
130
142
  "error": "account_id must be a numeric Merchant Center ID — "
@@ -125,7 +125,7 @@ These tools call both APIs internally and return unified results with computed `
125
125
  | Tool | When to Use | Key Parameters |
126
126
  |------|-------------|----------------|
127
127
  | `list_merchant_accounts` | Discovery — which Merchant Center accounts the connected user can reach | (none) |
128
- | `get_merchant_feed_health` | Shopping/PMax underperforming or dead — check feed disapprovals FIRST, before touching bids/budgets | `account_id` (numeric; from list_merchant_accounts) |
128
+ | `get_merchant_feed_health` | Shopping/PMax underperforming or dead — check feed disapprovals FIRST, before touching bids/budgets | `account_id` (numeric; from list_merchant_accounts — optional when `merchant.account_id` is configured) |
129
129
 
130
130
  - **Disapproved feed items are the #1 silent PMax/Shopping killer.** When a Shopping or PMax campaign has impressions collapsing or zero delivery, check `get_merchant_feed_health` before any campaign-side diagnosis.
131
131
  - The Merchant API has no read-only scope; AdLoop's merchant tools are read-only by design — never propose feed edits through these tools. Product-status data lags ~30 minutes — say so when the user just changed the feed.
@@ -718,7 +718,7 @@ def list_merchant_accounts() -> dict:
718
718
 
719
719
  @mcp.tool(title="Merchant Center feed health", annotations=_READONLY, tags={"merchant"})
720
720
  @_safe
721
- def get_merchant_feed_health(account_id: str) -> dict:
721
+ def get_merchant_feed_health(account_id: str = "") -> dict:
722
722
  """Merchant Center feed health — disapproved products + account issues.
723
723
 
724
724
  Disapproved feed items silently starve Shopping and Performance Max
@@ -728,6 +728,7 @@ def get_merchant_feed_health(account_id: str) -> dict:
728
728
  account-level issues — CRITICAL ones stop offers serving entirely.
729
729
 
730
730
  account_id: numeric Merchant Center ID from list_merchant_accounts.
731
+ Defaults to merchant.account_id in the config.
731
732
  Product-status data lags reality by ~30 minutes.
732
733
  """
733
734
  from adloop.merchant.read import get_merchant_feed_health as _impl
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