adloop 0.8.1__tar.gz → 0.9.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.8.1 → adloop-0.9.0}/PKG-INFO +27 -27
- {adloop-0.8.1 → adloop-0.9.0}/README.md +25 -25
- {adloop-0.8.1 → adloop-0.9.0}/pyproject.toml +2 -2
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/__init__.py +1 -1
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/forecast.py +27 -9
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/cli.py +24 -3
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/__main__.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/client.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/currency.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/enums.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/gaql.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/read.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ads/write.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/auth.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/bundled_credentials.json +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/config.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/crossref.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/diagnostics.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ga4/client.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ga4/reports.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/ga4/tracking.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/adloop.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/commands/analyze-performance.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules/commands/optimize-campaign.md +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/rules_install.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/safety/audit.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/safety/guards.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/safety/preview.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/server.py +0 -0
- {adloop-0.8.1 → adloop-0.9.0}/src/adloop/tracking.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: adloop
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: The AI command center for Google Ads, GA4, and tracking code.
|
|
5
5
|
Keywords: mcp,google-ads,google-analytics,ga4,cursor,marketing
|
|
6
6
|
Author: Daniel Klose
|
|
7
7
|
Author-email: Daniel Klose <info@daniel-klose.com>
|
|
@@ -26,7 +26,7 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
# AdLoop
|
|
28
28
|
|
|
29
|
-
**
|
|
29
|
+
**The AI command center for Google Ads, GA4, and tracking code.**
|
|
30
30
|
|
|
31
31
|
[](https://pypi.org/project/adloop/)
|
|
32
32
|
[](LICENSE)
|
|
@@ -196,16 +196,23 @@ AdLoop manages real ad spend, so safety is not optional.
|
|
|
196
196
|
|
|
197
197
|
## Setup
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
> **⚠️ Built-in OAuth credentials are temporarily unavailable while Google verification is pending.**
|
|
200
|
+
> Google limits unverified OAuth apps to 100 users, and AdLoop has reached that cap. New users will see a **"This app is blocked"** error if they pick the built-in option in the wizard.
|
|
201
|
+
>
|
|
202
|
+
> **What this means for you:** until Google completes verification, **bring your own Google Cloud project** — it takes ~5 minutes, has no user cap, and the `adloop init` wizard guides you through it. Status updates: [Discussion #13](https://github.com/kLOsk/adloop/discussions/13).
|
|
203
|
+
>
|
|
204
|
+
> *(Existing users whose tokens were already issued before the cap continue to work — only first-time sign-ins are blocked.)*
|
|
205
|
+
|
|
206
|
+
### Install
|
|
200
207
|
|
|
201
|
-
**
|
|
208
|
+
**From PyPI:**
|
|
202
209
|
|
|
203
210
|
```bash
|
|
204
211
|
pip install adloop
|
|
205
212
|
adloop init
|
|
206
213
|
```
|
|
207
214
|
|
|
208
|
-
**
|
|
215
|
+
**From source:**
|
|
209
216
|
|
|
210
217
|
```bash
|
|
211
218
|
git clone https://github.com/kLOsk/adloop.git
|
|
@@ -214,21 +221,19 @@ uv sync
|
|
|
214
221
|
uv run adloop init
|
|
215
222
|
```
|
|
216
223
|
|
|
217
|
-
|
|
224
|
+
### What `adloop init` does
|
|
218
225
|
|
|
219
|
-
|
|
220
|
-
> Google limits unverified OAuth apps to 100 users. AdLoop has reached that cap while awaiting Google's app verification. Until verification is complete, the built-in credentials will show a **"This app is blocked"** error for new users.
|
|
221
|
-
>
|
|
222
|
-
> **Workaround:** set up your own Google Cloud project using the [Advanced Setup](#advanced-setup-custom-google-cloud-project) instructions below (takes ~5 minutes). Your own project has no user cap and is the recommended setup path in the meantime.
|
|
226
|
+
The wizard defaults to the "bring your own Google Cloud project" path while verification is pending. It walks you through:
|
|
223
227
|
|
|
224
|
-
|
|
228
|
+
1. **Google Cloud setup** — creates a project, enables the three APIs, generates an OAuth client (see [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) below for the exact steps the wizard refers you to)
|
|
229
|
+
2. **Developer token** — from your Google Ads MCC ([API Center](https://ads.google.com/aw/apicenter))
|
|
230
|
+
3. **MCC Account ID** — your Manager Account ID (top bar in the MCC UI)
|
|
231
|
+
4. **OAuth sign-in** — opens a browser to sign in with Google (or prints a URL for headless servers)
|
|
232
|
+
5. **Auto-discovers your accounts** — finds your GA4 properties and Ads accounts automatically
|
|
233
|
+
6. **Safety defaults** — budget cap and dry-run preference
|
|
234
|
+
7. **Editor config snippets** — prints MCP configuration for both Cursor and Claude Code
|
|
225
235
|
|
|
226
|
-
|
|
227
|
-
2. **MCC Account ID** — your Manager Account ID (top bar in the MCC UI)
|
|
228
|
-
3. **OAuth sign-in** — opens a browser to sign in with Google (or prints a URL for headless servers)
|
|
229
|
-
4. **Auto-discovers your accounts** — finds your GA4 properties and Ads accounts automatically
|
|
230
|
-
5. **Safety defaults** — budget cap and dry-run preference
|
|
231
|
-
6. **Editor config snippets** — prints MCP configuration for both Cursor and Claude Code
|
|
236
|
+
The wizard does still offer AdLoop's built-in credentials as a non-default option for existing users whose tokens predate the cap. Picking that option for a brand-new Google account will fail at the consent screen — the wizard warns you about this before you choose.
|
|
232
237
|
|
|
233
238
|
### Requirements
|
|
234
239
|
|
|
@@ -258,12 +263,9 @@ A developer token is **always required** — even when using AdLoop's built-in O
|
|
|
258
263
|
|
|
259
264
|
Running on a server without a browser (VMs, Docker, SSH)? The wizard automatically detects this and falls back to a manual flow: it prints an authorization URL you can open on any device, then you paste the redirect URL back into the terminal.
|
|
260
265
|
|
|
261
|
-
###
|
|
262
|
-
|
|
263
|
-
<details>
|
|
264
|
-
<summary>Click to expand — only needed if you want to use your own GCP project instead of AdLoop's built-in credentials</summary>
|
|
266
|
+
### Custom Google Cloud Project Setup
|
|
265
267
|
|
|
266
|
-
|
|
268
|
+
This is the default path while built-in credentials are blocked by Google's 100-user cap. The wizard refers to these steps — do them in your browser before running `adloop init` (or while it waits at the OAuth prompt).
|
|
267
269
|
|
|
268
270
|
#### Step 1 — Google Cloud Project
|
|
269
271
|
|
|
@@ -335,8 +337,6 @@ If you'd rather manage things by hand instead, copy `.claude/rules/adloop.md` an
|
|
|
335
337
|
|
|
336
338
|
**Claude Desktop / claude.ai** has no programmatic rules location. Run `adloop install-rules` and it will print the rules content for you to paste into Project settings → Custom instructions on claude.ai.
|
|
337
339
|
|
|
338
|
-
</details>
|
|
339
|
-
|
|
340
340
|
### Use It
|
|
341
341
|
|
|
342
342
|
Ask your AI assistant things like:
|
|
@@ -411,7 +411,7 @@ What's been shipped and what's next:
|
|
|
411
411
|
- ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
|
|
412
412
|
- **Claude Desktop one-click install** — `adloop install claude-desktop` (and/or a `.dxt` extension bundle) that writes the AdLoop MCP entry into `claude_desktop_config.json` automatically, so Claude Desktop + Cowork users don't have to hand-edit JSON
|
|
413
413
|
- ~~PyPI package~~ ✓ — `pip install adloop`
|
|
414
|
-
- ~~Bundled OAuth credentials~~ ✓ — no Google Cloud project required
|
|
414
|
+
- ~~Bundled OAuth credentials~~ ✓ — no Google Cloud project required (**currently blocked at 100-user cap** pending Google verification; `adloop init` defaults to the [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) path until verification completes)
|
|
415
415
|
- ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
|
|
416
416
|
- ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
|
|
417
417
|
- **Community launch** — HN, Indie Hackers, r/cursor, Twitter
|
|
@@ -433,7 +433,7 @@ AdLoop runs entirely on your machine. No data is collected, stored, or transmitt
|
|
|
433
433
|
|
|
434
434
|
<div align="center">
|
|
435
435
|
|
|
436
|
-
**If AdLoop
|
|
436
|
+
**If AdLoop helps you run Google Ads, GA4, and tracking code from one place — [give it a star](https://github.com/kLOsk/adloop).**
|
|
437
437
|
|
|
438
438
|
Made by [@kLOsk](https://github.com/kLOsk) | [Privacy Policy](PRIVACY.md)
|
|
439
439
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# AdLoop
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**The AI command center for Google Ads, GA4, and tracking code.**
|
|
6
6
|
|
|
7
7
|
[](https://pypi.org/project/adloop/)
|
|
8
8
|
[](LICENSE)
|
|
@@ -172,16 +172,23 @@ AdLoop manages real ad spend, so safety is not optional.
|
|
|
172
172
|
|
|
173
173
|
## Setup
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
> **⚠️ Built-in OAuth credentials are temporarily unavailable while Google verification is pending.**
|
|
176
|
+
> Google limits unverified OAuth apps to 100 users, and AdLoop has reached that cap. New users will see a **"This app is blocked"** error if they pick the built-in option in the wizard.
|
|
177
|
+
>
|
|
178
|
+
> **What this means for you:** until Google completes verification, **bring your own Google Cloud project** — it takes ~5 minutes, has no user cap, and the `adloop init` wizard guides you through it. Status updates: [Discussion #13](https://github.com/kLOsk/adloop/discussions/13).
|
|
179
|
+
>
|
|
180
|
+
> *(Existing users whose tokens were already issued before the cap continue to work — only first-time sign-ins are blocked.)*
|
|
181
|
+
|
|
182
|
+
### Install
|
|
176
183
|
|
|
177
|
-
**
|
|
184
|
+
**From PyPI:**
|
|
178
185
|
|
|
179
186
|
```bash
|
|
180
187
|
pip install adloop
|
|
181
188
|
adloop init
|
|
182
189
|
```
|
|
183
190
|
|
|
184
|
-
**
|
|
191
|
+
**From source:**
|
|
185
192
|
|
|
186
193
|
```bash
|
|
187
194
|
git clone https://github.com/kLOsk/adloop.git
|
|
@@ -190,21 +197,19 @@ uv sync
|
|
|
190
197
|
uv run adloop init
|
|
191
198
|
```
|
|
192
199
|
|
|
193
|
-
|
|
200
|
+
### What `adloop init` does
|
|
194
201
|
|
|
195
|
-
|
|
196
|
-
> Google limits unverified OAuth apps to 100 users. AdLoop has reached that cap while awaiting Google's app verification. Until verification is complete, the built-in credentials will show a **"This app is blocked"** error for new users.
|
|
197
|
-
>
|
|
198
|
-
> **Workaround:** set up your own Google Cloud project using the [Advanced Setup](#advanced-setup-custom-google-cloud-project) instructions below (takes ~5 minutes). Your own project has no user cap and is the recommended setup path in the meantime.
|
|
202
|
+
The wizard defaults to the "bring your own Google Cloud project" path while verification is pending. It walks you through:
|
|
199
203
|
|
|
200
|
-
|
|
204
|
+
1. **Google Cloud setup** — creates a project, enables the three APIs, generates an OAuth client (see [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) below for the exact steps the wizard refers you to)
|
|
205
|
+
2. **Developer token** — from your Google Ads MCC ([API Center](https://ads.google.com/aw/apicenter))
|
|
206
|
+
3. **MCC Account ID** — your Manager Account ID (top bar in the MCC UI)
|
|
207
|
+
4. **OAuth sign-in** — opens a browser to sign in with Google (or prints a URL for headless servers)
|
|
208
|
+
5. **Auto-discovers your accounts** — finds your GA4 properties and Ads accounts automatically
|
|
209
|
+
6. **Safety defaults** — budget cap and dry-run preference
|
|
210
|
+
7. **Editor config snippets** — prints MCP configuration for both Cursor and Claude Code
|
|
201
211
|
|
|
202
|
-
|
|
203
|
-
2. **MCC Account ID** — your Manager Account ID (top bar in the MCC UI)
|
|
204
|
-
3. **OAuth sign-in** — opens a browser to sign in with Google (or prints a URL for headless servers)
|
|
205
|
-
4. **Auto-discovers your accounts** — finds your GA4 properties and Ads accounts automatically
|
|
206
|
-
5. **Safety defaults** — budget cap and dry-run preference
|
|
207
|
-
6. **Editor config snippets** — prints MCP configuration for both Cursor and Claude Code
|
|
212
|
+
The wizard does still offer AdLoop's built-in credentials as a non-default option for existing users whose tokens predate the cap. Picking that option for a brand-new Google account will fail at the consent screen — the wizard warns you about this before you choose.
|
|
208
213
|
|
|
209
214
|
### Requirements
|
|
210
215
|
|
|
@@ -234,12 +239,9 @@ A developer token is **always required** — even when using AdLoop's built-in O
|
|
|
234
239
|
|
|
235
240
|
Running on a server without a browser (VMs, Docker, SSH)? The wizard automatically detects this and falls back to a manual flow: it prints an authorization URL you can open on any device, then you paste the redirect URL back into the terminal.
|
|
236
241
|
|
|
237
|
-
###
|
|
238
|
-
|
|
239
|
-
<details>
|
|
240
|
-
<summary>Click to expand — only needed if you want to use your own GCP project instead of AdLoop's built-in credentials</summary>
|
|
242
|
+
### Custom Google Cloud Project Setup
|
|
241
243
|
|
|
242
|
-
|
|
244
|
+
This is the default path while built-in credentials are blocked by Google's 100-user cap. The wizard refers to these steps — do them in your browser before running `adloop init` (or while it waits at the OAuth prompt).
|
|
243
245
|
|
|
244
246
|
#### Step 1 — Google Cloud Project
|
|
245
247
|
|
|
@@ -311,8 +313,6 @@ If you'd rather manage things by hand instead, copy `.claude/rules/adloop.md` an
|
|
|
311
313
|
|
|
312
314
|
**Claude Desktop / claude.ai** has no programmatic rules location. Run `adloop install-rules` and it will print the rules content for you to paste into Project settings → Custom instructions on claude.ai.
|
|
313
315
|
|
|
314
|
-
</details>
|
|
315
|
-
|
|
316
316
|
### Use It
|
|
317
317
|
|
|
318
318
|
Ask your AI assistant things like:
|
|
@@ -387,7 +387,7 @@ What's been shipped and what's next:
|
|
|
387
387
|
- ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
|
|
388
388
|
- **Claude Desktop one-click install** — `adloop install claude-desktop` (and/or a `.dxt` extension bundle) that writes the AdLoop MCP entry into `claude_desktop_config.json` automatically, so Claude Desktop + Cowork users don't have to hand-edit JSON
|
|
389
389
|
- ~~PyPI package~~ ✓ — `pip install adloop`
|
|
390
|
-
- ~~Bundled OAuth credentials~~ ✓ — no Google Cloud project required
|
|
390
|
+
- ~~Bundled OAuth credentials~~ ✓ — no Google Cloud project required (**currently blocked at 100-user cap** pending Google verification; `adloop init` defaults to the [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) path until verification completes)
|
|
391
391
|
- ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
|
|
392
392
|
- ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
|
|
393
393
|
- **Community launch** — HN, Indie Hackers, r/cursor, Twitter
|
|
@@ -409,7 +409,7 @@ AdLoop runs entirely on your machine. No data is collected, stored, or transmitt
|
|
|
409
409
|
|
|
410
410
|
<div align="center">
|
|
411
411
|
|
|
412
|
-
**If AdLoop
|
|
412
|
+
**If AdLoop helps you run Google Ads, GA4, and tracking code from one place — [give it a star](https://github.com/kLOsk/adloop).**
|
|
413
413
|
|
|
414
414
|
Made by [@kLOsk](https://github.com/kLOsk) | [Privacy Policy](PRIVACY.md)
|
|
415
415
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "adloop"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.9.0"
|
|
4
|
+
description = "The AI command center for Google Ads, GA4, and tracking code."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "Daniel Klose", email = "info@daniel-klose.com" }
|
|
@@ -98,30 +98,43 @@ def estimate_budget(
|
|
|
98
98
|
response = kp_service.generate_keyword_forecast_metrics(request=request)
|
|
99
99
|
metrics = response.campaign_forecast_metrics
|
|
100
100
|
|
|
101
|
+
# KeywordForecastMetrics fields are ``optional`` in v23, so the SDK
|
|
102
|
+
# returns ``None`` for unset fields and the actual integer (including
|
|
103
|
+
# 0) otherwise. Falsy checks like ``int(v) if v else None`` would
|
|
104
|
+
# silently map a real 0-click or 0-cost forecast to None and the
|
|
105
|
+
# caller couldn't tell "no data" apart from "data says zero" — same
|
|
106
|
+
# bug class as discover_keywords (issue Bug 2). Use ``is not None``
|
|
107
|
+
# throughout and the shared ``_micros_to_currency`` helper for the
|
|
108
|
+
# micros→currency conversions.
|
|
101
109
|
clicks = getattr(metrics, "clicks", None)
|
|
102
110
|
impressions = getattr(metrics, "impressions", None)
|
|
103
111
|
avg_cpc_micros = getattr(metrics, "average_cpc_micros", None)
|
|
104
112
|
cost_micros = getattr(metrics, "cost_micros", None)
|
|
105
113
|
ctr = getattr(metrics, "click_through_rate", None)
|
|
106
114
|
|
|
107
|
-
total_cost =
|
|
108
|
-
avg_cpc =
|
|
115
|
+
total_cost = _micros_to_currency(cost_micros)
|
|
116
|
+
avg_cpc = _micros_to_currency(avg_cpc_micros)
|
|
109
117
|
|
|
110
118
|
days = max(forecast_days, 1)
|
|
111
119
|
daily = {
|
|
112
|
-
"clicks": round(clicks / days, 1) if clicks else None,
|
|
113
|
-
"impressions":
|
|
114
|
-
|
|
120
|
+
"clicks": round(clicks / days, 1) if clicks is not None else None,
|
|
121
|
+
"impressions": (
|
|
122
|
+
round(impressions / days, 1) if impressions is not None else None
|
|
123
|
+
),
|
|
124
|
+
"cost": round(total_cost / days, 2) if total_cost is not None else None,
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
insights = []
|
|
128
|
+
# Only emit the headline insight when there's actually something to
|
|
129
|
+
# report (real clicks AND real cost) — a zero-click forecast gets its
|
|
130
|
+
# own dedicated insight below, and a None forecast shouldn't trigger
|
|
131
|
+
# either path.
|
|
118
132
|
if total_cost is not None and clicks is not None and clicks > 0:
|
|
119
|
-
effective_cpa_budget = total_cost / clicks * 10
|
|
120
133
|
insights.append(
|
|
121
134
|
f"Estimated {clicks:.0f} clicks over {forecast_days} days at "
|
|
122
135
|
f"~{avg_cpc} avg CPC. Total estimated cost: {total_cost:.2f}."
|
|
123
136
|
)
|
|
124
|
-
if daily_budget > 0 and daily["cost"] is not None:
|
|
137
|
+
if daily_budget > 0 and daily["cost"] is not None and daily["cost"] > 0:
|
|
125
138
|
if daily_budget < daily["cost"]:
|
|
126
139
|
capture_pct = round(daily_budget / daily["cost"] * 100)
|
|
127
140
|
insights.append(
|
|
@@ -134,7 +147,12 @@ def estimate_budget(
|
|
|
134
147
|
f"most available traffic (estimated daily cost: {daily['cost']:.2f})."
|
|
135
148
|
)
|
|
136
149
|
|
|
137
|
-
if
|
|
150
|
+
if (
|
|
151
|
+
impressions is not None
|
|
152
|
+
and clicks is not None
|
|
153
|
+
and impressions > 0
|
|
154
|
+
and clicks == 0
|
|
155
|
+
):
|
|
138
156
|
insights.append(
|
|
139
157
|
"Forecast shows impressions but zero clicks — keywords may be too "
|
|
140
158
|
"generic or CPCs too low for competitive positions."
|
|
@@ -149,7 +167,7 @@ def estimate_budget(
|
|
|
149
167
|
"estimated_impressions": impressions,
|
|
150
168
|
"estimated_cost": total_cost,
|
|
151
169
|
"estimated_avg_cpc": avg_cpc,
|
|
152
|
-
"estimated_ctr": round(ctr, 4) if ctr else None,
|
|
170
|
+
"estimated_ctr": round(ctr, 4) if ctr is not None else None,
|
|
153
171
|
"daily_estimates": daily,
|
|
154
172
|
"keywords_used": len([kw for kw in keywords if kw.get("text")]),
|
|
155
173
|
"insights": insights,
|
|
@@ -319,12 +319,33 @@ def run_init_wizard() -> None:
|
|
|
319
319
|
return fallback
|
|
320
320
|
|
|
321
321
|
# Step 1: Credentials mode
|
|
322
|
+
#
|
|
323
|
+
# We used to default to AdLoop's bundled OAuth credentials (zero-GCP
|
|
324
|
+
# setup, "Use built-in credentials? (recommended)" Y/N with Y as default).
|
|
325
|
+
# That stopped being safe once Google's 100-user cap on unverified OAuth
|
|
326
|
+
# apps was reached — new users picking the bundled option now hit a
|
|
327
|
+
# "This app is blocked" error at the consent screen, leaving them
|
|
328
|
+
# stranded mid-wizard with no path forward.
|
|
329
|
+
#
|
|
330
|
+
# Until Google completes verification, the wizard defaults to the
|
|
331
|
+
# "bring your own Google Cloud project" path (which has no cap and
|
|
332
|
+
# works immediately) and only offers the bundled path as an opt-in
|
|
333
|
+
# for existing users whose tokens predate the cap. We surface the
|
|
334
|
+
# status explicitly so people understand WHY the default flipped
|
|
335
|
+
# rather than wondering whether the wizard is broken.
|
|
322
336
|
_step_header(1, "Google Credentials")
|
|
323
|
-
_print(" AdLoop
|
|
324
|
-
_print("
|
|
337
|
+
_print(" ⚠ AdLoop's built-in Google OAuth is currently blocked at")
|
|
338
|
+
_print(" Google's 100-user cap (pending OAuth verification).")
|
|
339
|
+
_print(" New sign-ins will fail with \"This app is blocked\".")
|
|
340
|
+
_print(" Status: https://github.com/kLOsk/adloop/discussions/13")
|
|
341
|
+
_print()
|
|
342
|
+
_print(" The wizard will set you up with your own Google Cloud")
|
|
343
|
+
_print(" project instead — takes ~5 minutes, no cap, works today.")
|
|
325
344
|
_print()
|
|
326
345
|
use_bundled = _prompt_bool(
|
|
327
|
-
"Use built-in credentials? (
|
|
346
|
+
"Use built-in credentials anyway? (only works if you already "
|
|
347
|
+
"authorized AdLoop before the cap)",
|
|
348
|
+
default=False,
|
|
328
349
|
)
|
|
329
350
|
|
|
330
351
|
credentials_path = ""
|
|
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
|