Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .claude/rules/adloop.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,13 @@ Conversions that happen offline (a sales call, a lead that turned into a deal) g

| Tool | What It Does | Validation |
|------|-------------|------------|
| `draft_campaign` | Create full campaign structure (budget + campaign + ad group + keywords + geo/language targeting) | `campaign_name`, `daily_budget`, `bidding_strategy`, `geo_target_ids` (REQUIRED), `language_ids` (REQUIRED), optional `search_partners_enabled`, `display_network_enabled`, `display_expansion_enabled`, optional `max_cpc` for MANUAL_CPC ad-group bids or TARGET_SPEND CPC caps, optional `location_option` / `negative_location_option` |
| `draft_campaign` | Create full campaign structure (budget + campaign + ad group + keywords + geo/language targeting) | `campaign_name`, `daily_budget`, `bidding_strategy`, `geo_target_ids` (REQUIRED), `language_ids` (REQUIRED), optional `search_partners_enabled`, `display_network_enabled`, `display_expansion_enabled`, optional `max_cpc` for MANUAL_CPC ad-group bids or TARGET_SPEND CPC caps, optional `location_option` / `negative_location_option`, optional `tracking_url_template` / `final_url_suffix` / `url_custom_parameters` |
| `draft_shopping_campaign` | Create a Standard Shopping campaign for a Merchant Center feed: budget + campaign (PAUSED) + product ad group + Shopping ad + one "All products" product group, atomically | `campaign_name`, `daily_budget`, `merchant_id` (must be linked to the Ads account; checked), `product_bid` (CPC of the product group, required by Google), `geo_target_ids` (REQUIRED), `bidding_strategy` MANUAL_CPC (default) / TARGET_SPEND (Maximize clicks, optional `max_cpc`) / TARGET_ROAS (+ `target_roas`), `campaign_priority` LOW/MEDIUM/HIGH, optional `feed_label`. No keywords, no languages |
| `draft_asset_group_assets` | Add and/or remove assets of an existing PMax asset group in one atomic change: text, images, YouTube videos | `asset_group_id`, `headlines` (≤30), `long_headlines` (≤90), `descriptions` (≤90), `business_name` (≤25), `images` (`{"url"}` or `{"upload_id"}`, optional `role` marketing/logo/landscape_logo; marketing images sorted into landscape 1.91:1, square 1:1, portrait 4:5 by ratio), `youtube_video_ids`, `remove_asset_ids` |
| `draft_asset_group_signals` | Add search themes or set the audience signal of a PMax asset group, or remove signals | `asset_group_id`, `add_search_themes` (≤80 chars, up to 50 per group), `audience_id` (one per group), `remove_signal_ids` |
| `draft_pmax_product_exclusions` | Set the full list of products a retail PMax asset group excludes, on one dimension; everything else stays included | `asset_group_id`, `exclude_values` (the COMPLETE list; `[]` includes everything again), `dimension` item_id (default) / brand / custom_label_0–4 |
| `draft_ad_group` | Create a new ad group within an existing campaign (does NOT publish) | `campaign_id` (REQUIRED), `ad_group_name` (REQUIRED), `keywords` (optional list of {text, match_type}), `cpc_bid_micros` (optional) |
| `update_campaign` | Modify existing campaign settings — bid strategy, budget, geo targets, language targets, location options, Search partners, display expansion | `campaign_id` (REQUIRED), plus any of: `bidding_strategy`, `daily_budget`, `geo_target_ids`, `language_ids`, `location_option`, `negative_location_option`, `search_partners_enabled`, `display_network_enabled`, TARGET_SPEND `max_cpc` |
| `update_campaign` | Modify existing campaign settings — bid strategy, budget, geo targets, language targets, location options, Search partners, display expansion, URL options | `campaign_id` (REQUIRED), plus any of: `bidding_strategy`, `daily_budget`, `geo_target_ids`, `language_ids`, `location_option`, `negative_location_option`, `search_partners_enabled`, `display_network_enabled`, TARGET_SPEND `max_cpc`, `tracking_url_template`, `final_url_suffix`, `url_custom_parameters` |
| `update_ad_group` | Update ad group name and/or MANUAL_CPC `max_cpc` | `ad_group_id`, optional `ad_group_name`, optional `max_cpc` |
| `draft_responsive_search_ad` | Create RSA preview (does NOT publish) | 3-15 headlines (≤30 chars), 2-4 descriptions (≤90 chars), final_url required, path1/path2 (≤15 chars each). Each headline/description may be a plain string (unpinned) or `{"text": "...", "pinned_field": "HEADLINE_1"}` (pinned). Valid pin slots: `HEADLINE_1/2/3`, `DESCRIPTION_1/2`. Several assets may pin to one slot (they rotate there), but every slot must stay fillable: by an asset pinned to it or by a distinct unpinned asset. |
| `draft_callouts` | Create callout assets for an ad group, a campaign or the account (does NOT publish) | `campaign_id` or `ad_group_id` (the scope follows the id), or `scope="account"`; `callouts` list with 1-25 chars each |
Expand Down Expand Up @@ -369,6 +369,7 @@ Same draft → preview → `confirm_and_apply` gate; the plan carries `platform:
- Ad-group pause/enable is already handled by `pause_entity` / `enable_entity` with `entity_type="ad_group"`; do not invent a separate pause tool.
- `update_campaign` budget changes show the current amount (`current_daily_budget`). A shared budget (`budget_shared_with` lists the other campaign IDs) or a portfolio bid strategy makes the plan require double confirmation, and apply refuses if the budget, its sharing or the portfolio changed after the preview.
- Location options: `location_option` is who the geo targets reach — `PRESENCE` (people in or regularly in the locations), `PRESENCE_OR_INTEREST` (Google's default; also people elsewhere who show interest in them) or `SEARCH_INTEREST`. `negative_location_option` (`PRESENCE` or `PRESENCE_OR_INTEREST`) is who the exclusions exclude. Empty leaves the value as it is. The preview warns when a change widens reach: for `location_option` that is anything but `PRESENCE`, for `negative_location_option` it is `PRESENCE`, because then the exclusions only keep out people physically there, not people interested in the excluded places; `update_campaign` shows the current value as `current_location_option` / `current_negative_location_option`. Read back with `get_campaign_settings`.
- Campaign URL options (`tracking_url_template`, `final_url_suffix`, `url_custom_parameters`): omitted leaves a value unchanged, `""` or `[]` removes it, and `url_custom_parameters` replaces the whole list. The template must contain `{lpurl}` or a variant (`{lpurl+2}`, `{unescapedlpurl}`, `{escapedlpurl+3}`, ...). Custom parameter keys are alphanumeric, at most 8 characters. The `update_campaign` preview shows each current value (`current_tracking_url_template` etc.), warns on a removal, and gives `example_click`: one of the campaign's final URLs with the campaign's options applied. It is approximate: a template on the ad group, ad or keyword overrides the campaign's, and ValueTrack parameters are filled at click time. Check it for typos before approving. Read back with `get_campaign_settings`.
- `update_campaign` replaces POSITIVE geo/language targets entirely (not append). Pass the full desired list. NEGATIVE geo exclusions (criteria with `negative=TRUE`) are **preserved** across a positive-geo replacement — they survive the swap. The preview surfaces preserved negative geo IDs in `preserved_negative_geo_target_ids` so the change is auditable. To ADD negative geo exclusions (e.g. exclude a city inside a targeted state), use `add_negative_locations` — do not try to express exclusions through `geo_target_ids`. To remove a negative geo exclusion explicitly, use `remove_entity` with `entity_type="campaign_criterion"`.
- **Asset scope:** callouts and snippets link where the id points (`ad_group_id` → ad group, `campaign_id` → campaign). Account-wide (`scope="account"`) reaches every campaign without assets of its own; use it only when the user asks for account level. **Asset edits** (`update_callout`, `update_sitelink`, `update_structured_snippet`) change the asset everywhere it is linked; check with GAQL (`campaign_asset`, `ad_group_asset`, `customer_asset`) where else it is used before editing a shared asset. If the apply reports the asset changed in Google Ads since the preview, draft again.
- **Call assets, schedules, promotions and prices.** Never guess `country_code` or `language_code`: take them from the number and the landing page, and ask when unclear. Money amounts are in the account currency (the preview names it); pass `currency_code` only for a different one. Ad schedule hours are in the **account time zone** (the previews show it), not the searcher's: convert the user's local hours first. A campaign without a schedule serves at all hours, so its first `add_ad_schedule` windows switch off every other hour and day: say so, the preview needs a double confirmation. To change a window, remove the old one with `remove_entity` (`entity_type="campaign_criterion"`, the `remove_id` from the preview), then add the new one. Prices must match the landing page or Google disapproves them.
Expand Down
5 changes: 3 additions & 2 deletions .cursor/rules/adloop.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,13 @@ Conversions that happen offline (a sales call, a lead that turned into a deal) g

| Tool | What It Does | Validation |
|------|-------------|------------|
| `draft_campaign` | Create full campaign structure (budget + campaign + ad group + keywords + geo/language targeting) | `campaign_name`, `daily_budget`, `bidding_strategy`, `geo_target_ids` (REQUIRED), `language_ids` (REQUIRED), optional `search_partners_enabled`, `display_network_enabled`, `display_expansion_enabled`, optional `max_cpc` for MANUAL_CPC ad-group bids or TARGET_SPEND CPC caps, optional `location_option` / `negative_location_option` |
| `draft_campaign` | Create full campaign structure (budget + campaign + ad group + keywords + geo/language targeting) | `campaign_name`, `daily_budget`, `bidding_strategy`, `geo_target_ids` (REQUIRED), `language_ids` (REQUIRED), optional `search_partners_enabled`, `display_network_enabled`, `display_expansion_enabled`, optional `max_cpc` for MANUAL_CPC ad-group bids or TARGET_SPEND CPC caps, optional `location_option` / `negative_location_option`, optional `tracking_url_template` / `final_url_suffix` / `url_custom_parameters` |
| `draft_shopping_campaign` | Create a Standard Shopping campaign for a Merchant Center feed: budget + campaign (PAUSED) + product ad group + Shopping ad + one "All products" product group, atomically | `campaign_name`, `daily_budget`, `merchant_id` (must be linked to the Ads account; checked), `product_bid` (CPC of the product group, required by Google), `geo_target_ids` (REQUIRED), `bidding_strategy` MANUAL_CPC (default) / TARGET_SPEND (Maximize clicks, optional `max_cpc`) / TARGET_ROAS (+ `target_roas`), `campaign_priority` LOW/MEDIUM/HIGH, optional `feed_label`. No keywords, no languages |
| `draft_asset_group_assets` | Add and/or remove assets of an existing PMax asset group in one atomic change: text, images, YouTube videos | `asset_group_id`, `headlines` (≤30), `long_headlines` (≤90), `descriptions` (≤90), `business_name` (≤25), `images` (`{"url"}` or `{"upload_id"}`, optional `role` marketing/logo/landscape_logo; marketing images sorted into landscape 1.91:1, square 1:1, portrait 4:5 by ratio), `youtube_video_ids`, `remove_asset_ids` |
| `draft_asset_group_signals` | Add search themes or set the audience signal of a PMax asset group, or remove signals | `asset_group_id`, `add_search_themes` (≤80 chars, up to 50 per group), `audience_id` (one per group), `remove_signal_ids` |
| `draft_pmax_product_exclusions` | Set the full list of products a retail PMax asset group excludes, on one dimension; everything else stays included | `asset_group_id`, `exclude_values` (the COMPLETE list; `[]` includes everything again), `dimension` item_id (default) / brand / custom_label_0–4 |
| `draft_ad_group` | Create a new ad group within an existing campaign (does NOT publish) | `campaign_id` (REQUIRED), `ad_group_name` (REQUIRED), `keywords` (optional list of {text, match_type}), `cpc_bid_micros` (optional) |
| `update_campaign` | Modify existing campaign settings — bid strategy, budget, geo targets, language targets, location options, Search partners, display expansion | `campaign_id` (REQUIRED), plus any of: `bidding_strategy`, `daily_budget`, `geo_target_ids`, `language_ids`, `location_option`, `negative_location_option`, `search_partners_enabled`, `display_network_enabled`, TARGET_SPEND `max_cpc` |
| `update_campaign` | Modify existing campaign settings — bid strategy, budget, geo targets, language targets, location options, Search partners, display expansion, URL options | `campaign_id` (REQUIRED), plus any of: `bidding_strategy`, `daily_budget`, `geo_target_ids`, `language_ids`, `location_option`, `negative_location_option`, `search_partners_enabled`, `display_network_enabled`, TARGET_SPEND `max_cpc`, `tracking_url_template`, `final_url_suffix`, `url_custom_parameters` |
| `update_ad_group` | Update ad group name and/or MANUAL_CPC `max_cpc` | `ad_group_id`, optional `ad_group_name`, optional `max_cpc` |
| `draft_responsive_search_ad` | Create RSA preview (does NOT publish) | 3-15 headlines (≤30 chars), 2-4 descriptions (≤90 chars), final_url required, path1/path2 (≤15 chars each). Each headline/description may be a plain string (unpinned) or `{"text": "...", "pinned_field": "HEADLINE_1"}` (pinned). Valid pin slots: `HEADLINE_1/2/3`, `DESCRIPTION_1/2`. Several assets may pin to one slot (they rotate there), but every slot must stay fillable: by an asset pinned to it or by a distinct unpinned asset. |
| `draft_callouts` | Create callout assets for an ad group, a campaign or the account (does NOT publish) | `campaign_id` or `ad_group_id` (the scope follows the id), or `scope="account"`; `callouts` list with 1-25 chars each |
Expand Down Expand Up @@ -371,6 +371,7 @@ Same draft → preview → `confirm_and_apply` gate; the plan carries `platform:
- Ad-group pause/enable is already handled by `pause_entity` / `enable_entity` with `entity_type="ad_group"`; do not invent a separate pause tool.
- `update_campaign` budget changes show the current amount (`current_daily_budget`). A shared budget (`budget_shared_with` lists the other campaign IDs) or a portfolio bid strategy makes the plan require double confirmation, and apply refuses if the budget, its sharing or the portfolio changed after the preview.
- Location options: `location_option` is who the geo targets reach — `PRESENCE` (people in or regularly in the locations), `PRESENCE_OR_INTEREST` (Google's default; also people elsewhere who show interest in them) or `SEARCH_INTEREST`. `negative_location_option` (`PRESENCE` or `PRESENCE_OR_INTEREST`) is who the exclusions exclude. Empty leaves the value as it is. The preview warns when a change widens reach: for `location_option` that is anything but `PRESENCE`, for `negative_location_option` it is `PRESENCE`, because then the exclusions only keep out people physically there, not people interested in the excluded places; `update_campaign` shows the current value as `current_location_option` / `current_negative_location_option`. Read back with `get_campaign_settings`.
- Campaign URL options (`tracking_url_template`, `final_url_suffix`, `url_custom_parameters`): omitted leaves a value unchanged, `""` or `[]` removes it, and `url_custom_parameters` replaces the whole list. The template must contain `{lpurl}` or a variant (`{lpurl+2}`, `{unescapedlpurl}`, `{escapedlpurl+3}`, ...). Custom parameter keys are alphanumeric, at most 8 characters. The `update_campaign` preview shows each current value (`current_tracking_url_template` etc.), warns on a removal, and gives `example_click`: one of the campaign's final URLs with the campaign's options applied. It is approximate: a template on the ad group, ad or keyword overrides the campaign's, and ValueTrack parameters are filled at click time. Check it for typos before approving. Read back with `get_campaign_settings`.
- `update_campaign` replaces POSITIVE geo/language targets entirely (not append). Pass the full desired list. NEGATIVE geo exclusions (criteria with `negative=TRUE`) are **preserved** across a positive-geo replacement — they survive the swap. The preview surfaces preserved negative geo IDs in `preserved_negative_geo_target_ids` so the change is auditable. To ADD negative geo exclusions (e.g. exclude a city inside a targeted state), use `add_negative_locations` — do not try to express exclusions through `geo_target_ids`. To remove a negative geo exclusion explicitly, use `remove_entity` with `entity_type="campaign_criterion"`.
- **Asset scope:** callouts and snippets link where the id points (`ad_group_id` → ad group, `campaign_id` → campaign). Account-wide (`scope="account"`) reaches every campaign without assets of its own; use it only when the user asks for account level. **Asset edits** (`update_callout`, `update_sitelink`, `update_structured_snippet`) change the asset everywhere it is linked; check with GAQL (`campaign_asset`, `ad_group_asset`, `customer_asset`) where else it is used before editing a shared asset. If the apply reports the asset changed in Google Ads since the preview, draft again.
- **Call assets, schedules, promotions and prices.** Never guess `country_code` or `language_code`: take them from the number and the landing page, and ask when unclear. Money amounts are in the account currency (the preview names it); pass `currency_code` only for a different one. Ad schedule hours are in the **account time zone** (the previews show it), not the searcher's: convert the user's local hours first. A campaign without a schedule serves at all hours, so its first `add_ad_schedule` windows switch off every other hour and day: say so, the preview needs a double confirmation. To change a window, remove the old one with `remove_entity` (`entity_type="campaign_criterion"`, the `remove_id` from the preview), then add the new one. Prices must match the landing page or Google disapproves them.
Expand Down
Loading
Loading